@thelacanians/vue-native-cli 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +43 -23
- package/native/android/README.md +205 -0
- package/native/android/VueNativeCore/build.gradle.kts +100 -0
- package/native/android/VueNativeCore/consumer-rules.pro +12 -0
- package/native/android/VueNativeCore/proguard-rules.pro +33 -0
- package/native/android/VueNativeCore/src/main/AndroidManifest.xml +17 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/ErrorOverlayView.kt +94 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/HotReloadManager.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSPolyfills.kt +652 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSRuntime.kt +207 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +417 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/ComponentRegistry.kt +76 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActionSheetFactory.kt +78 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActivityIndicatorFactory.kt +46 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VAlertDialogFactory.kt +84 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VButtonFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VCheckboxFactory.kt +93 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VDropdownFactory.kt +125 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VImageFactory.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VInputFactory.kt +210 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VKeyboardAvoidingFactory.kt +31 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +183 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPickerFactory.kt +57 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPressableFactory.kt +109 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VProgressBarFactory.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRadioFactory.kt +103 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRefreshControlFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRootFactory.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSafeAreaFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSectionListFactory.kt +144 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSegmentedControlFactory.kt +77 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +74 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VStatusBarFactory.kt +52 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSwitchFactory.kt +62 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VTextFactory.kt +53 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VVideoFactory.kt +191 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VWebViewFactory.kt +90 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/NativeComponentFactory.kt +40 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/VTextNodeView.kt +23 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/GestureHelper.kt +16 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/TouchableView.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AnimationModule.kt +292 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AppStateModule.kt +41 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AsyncStorageModule.kt +59 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AudioModule.kt +331 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BackgroundTaskModule.kt +166 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BiometryModule.kt +56 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CalendarModule.kt +198 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CameraModule.kt +64 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ClipboardModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ContactsModule.kt +288 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DatabaseModule.kt +229 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DeviceInfoModule.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/FileSystemModule.kt +193 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeolocationModule.kt +68 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HapticsModule.kt +61 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HttpModule.kt +111 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/KeyboardModule.kt +26 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/LinkingModule.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModule.kt +27 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +92 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NetworkModule.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NotificationsModule.kt +181 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/OTAModule.kt +255 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt +147 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PermissionsModule.kt +126 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SecureStorageModule.kt +51 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SensorsModule.kt +134 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ShareModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +160 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/WebSocketModule.kt +155 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +802 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Tags.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/VueNativeActivity.kt +169 -0
- package/native/android/VueNativeCore/src/main/res/values/ids.xml +8 -0
- package/native/android/app/build.gradle.kts +45 -0
- package/native/android/app/proguard-rules.pro +5 -0
- package/native/android/app/src/main/AndroidManifest.xml +25 -0
- package/native/android/app/src/main/assets/.gitkeep +0 -0
- package/native/android/app/src/main/kotlin/com/vuenative/example/counter/MainActivity.kt +14 -0
- package/native/android/app/src/main/res/layout/activity_main.xml +6 -0
- package/native/android/app/src/main/res/values/strings.xml +3 -0
- package/native/android/app/src/main/res/values/themes.xml +9 -0
- package/native/android/app/src/main/res/xml/network_security_config.xml +8 -0
- package/native/android/build.gradle.kts +6 -0
- package/native/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/native/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/native/android/gradle.properties +4 -0
- package/native/android/gradlew +87 -0
- package/native/android/gradlew.bat +48 -0
- package/native/android/settings.gradle.kts +20 -0
- package/native/ios/VueNativeCore/Package.resolved +23 -0
- package/native/ios/VueNativeCore/Package.swift +32 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/CertificatePinning.swift +132 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/ErrorOverlayView.swift +92 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/HotReloadManager.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSPolyfills.swift +711 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSRuntime.swift +421 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +891 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/VueNativeViewController.swift +88 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/ComponentRegistry.swift +193 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActionSheetFactory.swift +91 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActivityIndicatorFactory.swift +74 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VAlertDialogFactory.swift +150 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VButtonFactory.swift +93 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VCheckboxFactory.swift +114 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VDropdownFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VImageFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VInputFactory.swift +357 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VKeyboardAvoidingFactory.swift +99 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +250 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VModalFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPickerFactory.swift +96 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPressableFactory.swift +168 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VProgressBarFactory.swift +39 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRadioFactory.swift +167 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRefreshControlFactory.swift +153 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSafeAreaFactory.swift +56 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSectionListFactory.swift +248 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSegmentedControlFactory.swift +73 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +63 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VStatusBarFactory.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSwitchFactory.swift +108 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +290 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VVideoFactory.swift +246 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +157 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VWebViewFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/NativeComponentFactory.swift +53 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +107 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/TouchableView.swift +136 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/UIColor+Hex.swift +80 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AnimationModule.swift +291 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AppStateModule.swift +65 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AsyncStorageModule.swift +68 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AudioModule.swift +366 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BackgroundTaskModule.swift +135 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BiometryModule.swift +61 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BluetoothModule.swift +387 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CalendarModule.swift +161 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CameraModule.swift +318 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ClipboardModule.swift +33 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ContactsModule.swift +173 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DatabaseModule.swift +259 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DeviceInfoModule.swift +34 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/FileSystemModule.swift +233 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeolocationModule.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/HapticsModule.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/IAPModule.swift +194 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/KeyboardModule.swift +31 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/LinkingModule.swift +42 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModule.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +78 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NetworkModule.swift +62 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NotificationsModule.swift +215 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/OTAModule.swift +281 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PerformanceModule.swift +138 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PermissionsModule.swift +190 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SecureStorageModule.swift +118 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SensorsModule.swift +103 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ShareModule.swift +49 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SocialAuthModule.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/WebSocketModule.swift +213 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Resources/vue-native-placeholder.js +8 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Styling/StyleEngine.swift +885 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSRuntimeTests.swift +362 -0
- package/package.json +3 -2
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import Foundation
|
|
3
|
+
import SQLite3
|
|
4
|
+
|
|
5
|
+
/// Native module for SQLite database access.
|
|
6
|
+
/// Uses the sqlite3 C API (built into iOS, no external dependencies).
|
|
7
|
+
/// Supports multiple named databases, parameterized queries, and transactions.
|
|
8
|
+
final class DatabaseModule: NativeModule {
|
|
9
|
+
let moduleName = "Database"
|
|
10
|
+
|
|
11
|
+
/// Open database handles keyed by database name.
|
|
12
|
+
private var databases: [String: OpaquePointer] = [:]
|
|
13
|
+
|
|
14
|
+
/// Directory for database files.
|
|
15
|
+
private var dbDirectory: URL {
|
|
16
|
+
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
17
|
+
.appendingPathComponent("databases", isDirectory: true)
|
|
18
|
+
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
19
|
+
return dir
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
23
|
+
switch method {
|
|
24
|
+
case "open":
|
|
25
|
+
let name = args.first as? String ?? "default"
|
|
26
|
+
open(name: name, callback: callback)
|
|
27
|
+
case "close":
|
|
28
|
+
let name = args.first as? String ?? "default"
|
|
29
|
+
close(name: name, callback: callback)
|
|
30
|
+
case "execute":
|
|
31
|
+
let name = args.count > 0 ? (args[0] as? String ?? "default") : "default"
|
|
32
|
+
let sql = args.count > 1 ? (args[1] as? String ?? "") : ""
|
|
33
|
+
let params = args.count > 2 ? (args[2] as? [Any] ?? []) : []
|
|
34
|
+
execute(name: name, sql: sql, params: params, callback: callback)
|
|
35
|
+
case "query":
|
|
36
|
+
let name = args.count > 0 ? (args[0] as? String ?? "default") : "default"
|
|
37
|
+
let sql = args.count > 1 ? (args[1] as? String ?? "") : ""
|
|
38
|
+
let params = args.count > 2 ? (args[2] as? [Any] ?? []) : []
|
|
39
|
+
query(name: name, sql: sql, params: params, callback: callback)
|
|
40
|
+
case "executeTransaction":
|
|
41
|
+
let name = args.count > 0 ? (args[0] as? String ?? "default") : "default"
|
|
42
|
+
let statements = args.count > 1 ? (args[1] as? [[String: Any]] ?? []) : []
|
|
43
|
+
executeTransaction(name: name, statements: statements, callback: callback)
|
|
44
|
+
default:
|
|
45
|
+
callback(nil, "DatabaseModule: unknown method '\(method)'")
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: - Open / Close
|
|
50
|
+
|
|
51
|
+
private func open(name: String, callback: @escaping (Any?, String?) -> Void) {
|
|
52
|
+
if databases[name] != nil {
|
|
53
|
+
callback(true, nil)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let path = dbDirectory.appendingPathComponent("\(name).sqlite").path
|
|
58
|
+
var db: OpaquePointer?
|
|
59
|
+
|
|
60
|
+
let flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX
|
|
61
|
+
let result = sqlite3_open_v2(path, &db, flags, nil)
|
|
62
|
+
if result == SQLITE_OK, let db = db {
|
|
63
|
+
// Enable WAL mode for better concurrent read/write performance
|
|
64
|
+
sqlite3_exec(db, "PRAGMA journal_mode=WAL", nil, nil, nil)
|
|
65
|
+
databases[name] = db
|
|
66
|
+
callback(true, nil)
|
|
67
|
+
} else {
|
|
68
|
+
let errorMsg = db != nil ? String(cString: sqlite3_errmsg(db)) : "Unknown error"
|
|
69
|
+
if db != nil { sqlite3_close(db) }
|
|
70
|
+
callback(nil, "Failed to open database '\(name)': \(errorMsg)")
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private func close(name: String, callback: @escaping (Any?, String?) -> Void) {
|
|
75
|
+
guard let db = databases.removeValue(forKey: name) else {
|
|
76
|
+
callback(nil, nil)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
sqlite3_close(db)
|
|
80
|
+
callback(nil, nil)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// MARK: - Execute (INSERT, UPDATE, DELETE, CREATE TABLE, etc.)
|
|
84
|
+
|
|
85
|
+
private func execute(name: String, sql: String, params: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
86
|
+
guard let db = getOrOpen(name: name, callback: callback) else { return }
|
|
87
|
+
|
|
88
|
+
var stmt: OpaquePointer?
|
|
89
|
+
guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {
|
|
90
|
+
let err = String(cString: sqlite3_errmsg(db))
|
|
91
|
+
callback(nil, "SQL prepare error: \(err)")
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
defer { sqlite3_finalize(stmt) }
|
|
95
|
+
|
|
96
|
+
bindParams(stmt: stmt!, params: params)
|
|
97
|
+
|
|
98
|
+
let stepResult = sqlite3_step(stmt)
|
|
99
|
+
if stepResult == SQLITE_DONE || stepResult == SQLITE_ROW {
|
|
100
|
+
let rowsAffected = sqlite3_changes(db)
|
|
101
|
+
let lastInsertId = sqlite3_last_insert_rowid(db)
|
|
102
|
+
var result: [String: Any] = ["rowsAffected": Int(rowsAffected)]
|
|
103
|
+
if lastInsertId > 0 {
|
|
104
|
+
result["insertId"] = Int(lastInsertId)
|
|
105
|
+
}
|
|
106
|
+
callback(result, nil)
|
|
107
|
+
} else {
|
|
108
|
+
let err = String(cString: sqlite3_errmsg(db))
|
|
109
|
+
callback(nil, "SQL execute error: \(err)")
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// MARK: - Query (SELECT)
|
|
114
|
+
|
|
115
|
+
private func query(name: String, sql: String, params: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
116
|
+
guard let db = getOrOpen(name: name, callback: callback) else { return }
|
|
117
|
+
|
|
118
|
+
var stmt: OpaquePointer?
|
|
119
|
+
guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {
|
|
120
|
+
let err = String(cString: sqlite3_errmsg(db))
|
|
121
|
+
callback(nil, "SQL prepare error: \(err)")
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
defer { sqlite3_finalize(stmt) }
|
|
125
|
+
|
|
126
|
+
bindParams(stmt: stmt!, params: params)
|
|
127
|
+
|
|
128
|
+
var rows: [[String: Any]] = []
|
|
129
|
+
let columnCount = sqlite3_column_count(stmt)
|
|
130
|
+
|
|
131
|
+
while sqlite3_step(stmt) == SQLITE_ROW {
|
|
132
|
+
var row: [String: Any] = [:]
|
|
133
|
+
for i in 0..<columnCount {
|
|
134
|
+
let colName = String(cString: sqlite3_column_name(stmt, i))
|
|
135
|
+
row[colName] = columnValue(stmt: stmt!, index: i)
|
|
136
|
+
}
|
|
137
|
+
rows.append(row)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
callback(rows, nil)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// MARK: - Transaction
|
|
144
|
+
|
|
145
|
+
private func executeTransaction(name: String, statements: [[String: Any]], callback: @escaping (Any?, String?) -> Void) {
|
|
146
|
+
guard let db = getOrOpen(name: name, callback: callback) else { return }
|
|
147
|
+
|
|
148
|
+
sqlite3_exec(db, "BEGIN TRANSACTION", nil, nil, nil)
|
|
149
|
+
|
|
150
|
+
var results: [[String: Any]] = []
|
|
151
|
+
|
|
152
|
+
for stmtData in statements {
|
|
153
|
+
let sql = stmtData["sql"] as? String ?? ""
|
|
154
|
+
let params = stmtData["params"] as? [Any] ?? []
|
|
155
|
+
|
|
156
|
+
var stmt: OpaquePointer?
|
|
157
|
+
guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {
|
|
158
|
+
let err = String(cString: sqlite3_errmsg(db))
|
|
159
|
+
sqlite3_exec(db, "ROLLBACK", nil, nil, nil)
|
|
160
|
+
callback(nil, "Transaction SQL prepare error: \(err)")
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
bindParams(stmt: stmt!, params: params)
|
|
165
|
+
|
|
166
|
+
let stepResult = sqlite3_step(stmt)
|
|
167
|
+
sqlite3_finalize(stmt)
|
|
168
|
+
|
|
169
|
+
if stepResult == SQLITE_DONE || stepResult == SQLITE_ROW {
|
|
170
|
+
let rowsAffected = sqlite3_changes(db)
|
|
171
|
+
let lastInsertId = sqlite3_last_insert_rowid(db)
|
|
172
|
+
var result: [String: Any] = ["rowsAffected": Int(rowsAffected)]
|
|
173
|
+
if lastInsertId > 0 {
|
|
174
|
+
result["insertId"] = Int(lastInsertId)
|
|
175
|
+
}
|
|
176
|
+
results.append(result)
|
|
177
|
+
} else {
|
|
178
|
+
let err = String(cString: sqlite3_errmsg(db))
|
|
179
|
+
sqlite3_exec(db, "ROLLBACK", nil, nil, nil)
|
|
180
|
+
callback(nil, "Transaction execute error: \(err)")
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
sqlite3_exec(db, "COMMIT", nil, nil, nil)
|
|
186
|
+
callback(results, nil)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// MARK: - Helpers
|
|
190
|
+
|
|
191
|
+
/// Get an existing database handle or auto-open it.
|
|
192
|
+
private func getOrOpen(name: String, callback: @escaping (Any?, String?) -> Void) -> OpaquePointer? {
|
|
193
|
+
if let db = databases[name] { return db }
|
|
194
|
+
|
|
195
|
+
// Auto-open
|
|
196
|
+
let path = dbDirectory.appendingPathComponent("\(name).sqlite").path
|
|
197
|
+
var db: OpaquePointer?
|
|
198
|
+
let flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX
|
|
199
|
+
let result = sqlite3_open_v2(path, &db, flags, nil)
|
|
200
|
+
if result == SQLITE_OK, let db = db {
|
|
201
|
+
sqlite3_exec(db, "PRAGMA journal_mode=WAL", nil, nil, nil)
|
|
202
|
+
databases[name] = db
|
|
203
|
+
return db
|
|
204
|
+
} else {
|
|
205
|
+
let errorMsg = db != nil ? String(cString: sqlite3_errmsg(db)) : "Unknown error"
|
|
206
|
+
if db != nil { sqlite3_close(db) }
|
|
207
|
+
callback(nil, "Failed to auto-open database '\(name)': \(errorMsg)")
|
|
208
|
+
return nil
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Bind parameter array to a prepared statement. Supports String, Int, Double, Bool, nil/NSNull.
|
|
213
|
+
private func bindParams(stmt: OpaquePointer, params: [Any]) {
|
|
214
|
+
for (i, param) in params.enumerated() {
|
|
215
|
+
let idx = Int32(i + 1)
|
|
216
|
+
if param is NSNull {
|
|
217
|
+
sqlite3_bind_null(stmt, idx)
|
|
218
|
+
} else if let s = param as? String {
|
|
219
|
+
sqlite3_bind_text(stmt, idx, (s as NSString).utf8String, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
220
|
+
} else if let n = param as? Int {
|
|
221
|
+
sqlite3_bind_int64(stmt, idx, Int64(n))
|
|
222
|
+
} else if let d = param as? Double {
|
|
223
|
+
sqlite3_bind_double(stmt, idx, d)
|
|
224
|
+
} else if let b = param as? Bool {
|
|
225
|
+
sqlite3_bind_int(stmt, idx, b ? 1 : 0)
|
|
226
|
+
} else {
|
|
227
|
+
// Fallback: bind as text
|
|
228
|
+
let str = "\(param)"
|
|
229
|
+
sqlite3_bind_text(stmt, idx, (str as NSString).utf8String, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// Extract a column value from the current row of a statement.
|
|
235
|
+
private func columnValue(stmt: OpaquePointer, index: Int32) -> Any {
|
|
236
|
+
let type = sqlite3_column_type(stmt, index)
|
|
237
|
+
switch type {
|
|
238
|
+
case SQLITE_INTEGER:
|
|
239
|
+
return Int(sqlite3_column_int64(stmt, index))
|
|
240
|
+
case SQLITE_FLOAT:
|
|
241
|
+
return sqlite3_column_double(stmt, index)
|
|
242
|
+
case SQLITE_TEXT:
|
|
243
|
+
return String(cString: sqlite3_column_text(stmt, index))
|
|
244
|
+
case SQLITE_BLOB:
|
|
245
|
+
// Return blob as base64 string
|
|
246
|
+
if let data = sqlite3_column_blob(stmt, index) {
|
|
247
|
+
let bytes = sqlite3_column_bytes(stmt, index)
|
|
248
|
+
let d = Data(bytes: data, count: Int(bytes))
|
|
249
|
+
return d.base64EncodedString()
|
|
250
|
+
}
|
|
251
|
+
return NSNull()
|
|
252
|
+
case SQLITE_NULL:
|
|
253
|
+
return NSNull()
|
|
254
|
+
default:
|
|
255
|
+
return NSNull()
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
#endif
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Native module providing device and screen information.
|
|
5
|
+
///
|
|
6
|
+
/// Methods:
|
|
7
|
+
/// - getInfo() -> { model, systemVersion, screenWidth, screenHeight, scale }
|
|
8
|
+
final class DeviceInfoModule: NativeModule {
|
|
9
|
+
let moduleName = "DeviceInfo"
|
|
10
|
+
|
|
11
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
12
|
+
DispatchQueue.main.async {
|
|
13
|
+
switch method {
|
|
14
|
+
case "getInfo":
|
|
15
|
+
let device = UIDevice.current
|
|
16
|
+
let screen = UIScreen.main
|
|
17
|
+
let info: [String: Any] = [
|
|
18
|
+
"model": device.model,
|
|
19
|
+
"systemVersion": device.systemVersion,
|
|
20
|
+
"systemName": device.systemName,
|
|
21
|
+
"name": device.name,
|
|
22
|
+
"screenWidth": screen.bounds.width,
|
|
23
|
+
"screenHeight": screen.bounds.height,
|
|
24
|
+
"scale": screen.scale
|
|
25
|
+
]
|
|
26
|
+
callback(info, nil)
|
|
27
|
+
|
|
28
|
+
default:
|
|
29
|
+
callback(nil, "DeviceInfoModule: Unknown method '\(method)'")
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
#endif
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Native module providing file system access.
|
|
5
|
+
///
|
|
6
|
+
/// Methods:
|
|
7
|
+
/// - readFile(path: String, encoding: String?) -> String
|
|
8
|
+
/// - writeFile(path: String, content: String, encoding: String?)
|
|
9
|
+
/// - deleteFile(path: String)
|
|
10
|
+
/// - exists(path: String) -> Bool
|
|
11
|
+
/// - listDirectory(path: String) -> [String]
|
|
12
|
+
/// - downloadFile(url: String, destPath: String) -> String
|
|
13
|
+
/// - getDocumentsPath() -> String
|
|
14
|
+
/// - getCachesPath() -> String
|
|
15
|
+
/// - stat(path: String) -> { size, isDirectory, modified }
|
|
16
|
+
/// - mkdir(path: String)
|
|
17
|
+
/// - copyFile(srcPath: String, destPath: String)
|
|
18
|
+
/// - moveFile(srcPath: String, destPath: String)
|
|
19
|
+
final class FileSystemModule: NativeModule {
|
|
20
|
+
let moduleName = "FileSystem"
|
|
21
|
+
|
|
22
|
+
private let fileManager = FileManager.default
|
|
23
|
+
|
|
24
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
25
|
+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
26
|
+
guard let self = self else { return }
|
|
27
|
+
switch method {
|
|
28
|
+
case "readFile":
|
|
29
|
+
guard let path = args.first as? String else {
|
|
30
|
+
callback(nil, "readFile: missing path")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
let encoding = (args.count > 1 ? args[1] as? String : nil) ?? "utf8"
|
|
34
|
+
guard self.fileManager.fileExists(atPath: path) else {
|
|
35
|
+
callback(nil, "readFile: file not found at \(path)")
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
guard let data = self.fileManager.contents(atPath: path) else {
|
|
39
|
+
callback(nil, "readFile: could not read file at \(path)")
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if encoding == "base64" {
|
|
43
|
+
callback(data.base64EncodedString(), nil)
|
|
44
|
+
} else {
|
|
45
|
+
guard let text = String(data: data, encoding: .utf8) else {
|
|
46
|
+
callback(nil, "readFile: file is not valid UTF-8")
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
callback(text, nil)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case "writeFile":
|
|
53
|
+
guard args.count >= 2,
|
|
54
|
+
let path = args[0] as? String,
|
|
55
|
+
let content = args[1] as? String else {
|
|
56
|
+
callback(nil, "writeFile: missing path or content")
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
let encoding = (args.count > 2 ? args[2] as? String : nil) ?? "utf8"
|
|
60
|
+
let data: Data?
|
|
61
|
+
if encoding == "base64" {
|
|
62
|
+
data = Data(base64Encoded: content)
|
|
63
|
+
} else {
|
|
64
|
+
data = content.data(using: .utf8)
|
|
65
|
+
}
|
|
66
|
+
guard let fileData = data else {
|
|
67
|
+
callback(nil, "writeFile: could not encode content")
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
// Create parent directory if needed
|
|
71
|
+
let dir = (path as NSString).deletingLastPathComponent
|
|
72
|
+
if !self.fileManager.fileExists(atPath: dir) {
|
|
73
|
+
do {
|
|
74
|
+
try self.fileManager.createDirectory(atPath: dir, withIntermediateDirectories: true)
|
|
75
|
+
} catch {
|
|
76
|
+
callback(nil, "writeFile: could not create directory: \(error.localizedDescription)")
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
self.fileManager.createFile(atPath: path, contents: fileData)
|
|
81
|
+
callback(nil, nil)
|
|
82
|
+
|
|
83
|
+
case "deleteFile":
|
|
84
|
+
guard let path = args.first as? String else {
|
|
85
|
+
callback(nil, "deleteFile: missing path")
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
guard self.fileManager.fileExists(atPath: path) else {
|
|
89
|
+
callback(nil, "deleteFile: file not found at \(path)")
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
do {
|
|
93
|
+
try self.fileManager.removeItem(atPath: path)
|
|
94
|
+
callback(nil, nil)
|
|
95
|
+
} catch {
|
|
96
|
+
callback(nil, "deleteFile: \(error.localizedDescription)")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case "exists":
|
|
100
|
+
guard let path = args.first as? String else {
|
|
101
|
+
callback(nil, "exists: missing path")
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
callback(self.fileManager.fileExists(atPath: path), nil)
|
|
105
|
+
|
|
106
|
+
case "listDirectory":
|
|
107
|
+
guard let path = args.first as? String else {
|
|
108
|
+
callback(nil, "listDirectory: missing path")
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
do {
|
|
112
|
+
let contents = try self.fileManager.contentsOfDirectory(atPath: path)
|
|
113
|
+
callback(contents, nil)
|
|
114
|
+
} catch {
|
|
115
|
+
callback(nil, "listDirectory: \(error.localizedDescription)")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case "downloadFile":
|
|
119
|
+
guard args.count >= 2,
|
|
120
|
+
let urlString = args[0] as? String,
|
|
121
|
+
let destPath = args[1] as? String else {
|
|
122
|
+
callback(nil, "downloadFile: missing url or destPath")
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
guard let url = URL(string: urlString) else {
|
|
126
|
+
callback(nil, "downloadFile: invalid URL")
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
|
130
|
+
if let error = error {
|
|
131
|
+
callback(nil, "downloadFile: \(error.localizedDescription)")
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
guard let data = data else {
|
|
135
|
+
callback(nil, "downloadFile: no data received")
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
// Create parent directory if needed
|
|
139
|
+
let dir = (destPath as NSString).deletingLastPathComponent
|
|
140
|
+
if !self.fileManager.fileExists(atPath: dir) {
|
|
141
|
+
do {
|
|
142
|
+
try self.fileManager.createDirectory(atPath: dir, withIntermediateDirectories: true)
|
|
143
|
+
} catch {
|
|
144
|
+
callback(nil, "downloadFile: could not create directory: \(error.localizedDescription)")
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
self.fileManager.createFile(atPath: destPath, contents: data)
|
|
149
|
+
callback(destPath, nil)
|
|
150
|
+
}
|
|
151
|
+
task.resume()
|
|
152
|
+
|
|
153
|
+
case "getDocumentsPath":
|
|
154
|
+
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
|
155
|
+
callback(paths.first, nil)
|
|
156
|
+
|
|
157
|
+
case "getCachesPath":
|
|
158
|
+
let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
|
|
159
|
+
callback(paths.first, nil)
|
|
160
|
+
|
|
161
|
+
case "stat":
|
|
162
|
+
guard let path = args.first as? String else {
|
|
163
|
+
callback(nil, "stat: missing path")
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
do {
|
|
167
|
+
let attrs = try self.fileManager.attributesOfItem(atPath: path)
|
|
168
|
+
let size = (attrs[.size] as? Int) ?? 0
|
|
169
|
+
let isDir = (attrs[.type] as? FileAttributeType) == .typeDirectory
|
|
170
|
+
let modified = (attrs[.modificationDate] as? Date)?.timeIntervalSince1970 ?? 0
|
|
171
|
+
callback([
|
|
172
|
+
"size": size,
|
|
173
|
+
"isDirectory": isDir,
|
|
174
|
+
"modified": modified * 1000 // milliseconds for JS
|
|
175
|
+
] as [String: Any], nil)
|
|
176
|
+
} catch {
|
|
177
|
+
callback(nil, "stat: \(error.localizedDescription)")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case "mkdir":
|
|
181
|
+
guard let path = args.first as? String else {
|
|
182
|
+
callback(nil, "mkdir: missing path")
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
do {
|
|
186
|
+
try self.fileManager.createDirectory(atPath: path, withIntermediateDirectories: true)
|
|
187
|
+
callback(nil, nil)
|
|
188
|
+
} catch {
|
|
189
|
+
callback(nil, "mkdir: \(error.localizedDescription)")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case "copyFile":
|
|
193
|
+
guard args.count >= 2,
|
|
194
|
+
let srcPath = args[0] as? String,
|
|
195
|
+
let destPath = args[1] as? String else {
|
|
196
|
+
callback(nil, "copyFile: missing srcPath or destPath")
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
do {
|
|
200
|
+
// Remove destination if it exists (copyItem throws if dest exists)
|
|
201
|
+
if self.fileManager.fileExists(atPath: destPath) {
|
|
202
|
+
try self.fileManager.removeItem(atPath: destPath)
|
|
203
|
+
}
|
|
204
|
+
try self.fileManager.copyItem(atPath: srcPath, toPath: destPath)
|
|
205
|
+
callback(nil, nil)
|
|
206
|
+
} catch {
|
|
207
|
+
callback(nil, "copyFile: \(error.localizedDescription)")
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
case "moveFile":
|
|
211
|
+
guard args.count >= 2,
|
|
212
|
+
let srcPath = args[0] as? String,
|
|
213
|
+
let destPath = args[1] as? String else {
|
|
214
|
+
callback(nil, "moveFile: missing srcPath or destPath")
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
do {
|
|
218
|
+
if self.fileManager.fileExists(atPath: destPath) {
|
|
219
|
+
try self.fileManager.removeItem(atPath: destPath)
|
|
220
|
+
}
|
|
221
|
+
try self.fileManager.moveItem(atPath: srcPath, toPath: destPath)
|
|
222
|
+
callback(nil, nil)
|
|
223
|
+
} catch {
|
|
224
|
+
callback(nil, "moveFile: \(error.localizedDescription)")
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
default:
|
|
228
|
+
callback(nil, "FileSystemModule: Unknown method '\(method)'")
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
#endif
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import CoreLocation
|
|
4
|
+
|
|
5
|
+
/// Native module for GPS/location access.
|
|
6
|
+
///
|
|
7
|
+
/// Methods:
|
|
8
|
+
/// - getCurrentPosition() -> location payload
|
|
9
|
+
/// - watchPosition() -> watchId (Int); fires "location:update" global events
|
|
10
|
+
/// - clearWatch(watchId: Int)
|
|
11
|
+
///
|
|
12
|
+
/// Location payload keys: latitude, longitude, altitude, accuracy,
|
|
13
|
+
/// altitudeAccuracy, heading, speed, timestamp
|
|
14
|
+
final class GeolocationModule: NativeModule {
|
|
15
|
+
var moduleName: String { "Geolocation" }
|
|
16
|
+
private weak var bridge: NativeBridge?
|
|
17
|
+
|
|
18
|
+
init(bridge: NativeBridge) { self.bridge = bridge }
|
|
19
|
+
|
|
20
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
21
|
+
switch method {
|
|
22
|
+
case "getCurrentPosition":
|
|
23
|
+
DispatchQueue.main.async {
|
|
24
|
+
GeolocationManager.shared.getCurrentPosition(callback: callback)
|
|
25
|
+
}
|
|
26
|
+
case "watchPosition":
|
|
27
|
+
let weakBridge = bridge
|
|
28
|
+
DispatchQueue.main.async {
|
|
29
|
+
let watchId = GeolocationManager.shared.watchPosition(bridge: weakBridge)
|
|
30
|
+
callback(watchId, nil)
|
|
31
|
+
}
|
|
32
|
+
case "clearWatch":
|
|
33
|
+
guard let watchId = args.first.flatMap({ $0 as? Int }) else {
|
|
34
|
+
callback(nil, nil); return
|
|
35
|
+
}
|
|
36
|
+
DispatchQueue.main.async {
|
|
37
|
+
GeolocationManager.shared.clearWatch(watchId)
|
|
38
|
+
callback(nil, nil)
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
callback(nil, "GeolocationModule: Unknown method '\(method)'")
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func invokeSync(method: String, args: [Any]) -> Any? { nil }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: - Internal location manager
|
|
49
|
+
|
|
50
|
+
/// All CLLocationManager interactions happen on @MainActor (main thread),
|
|
51
|
+
/// matching the UIKit + CoreLocation threading requirement.
|
|
52
|
+
@MainActor
|
|
53
|
+
private final class GeolocationManager: NSObject, CLLocationManagerDelegate {
|
|
54
|
+
static let shared = GeolocationManager()
|
|
55
|
+
|
|
56
|
+
private var manager: CLLocationManager?
|
|
57
|
+
private var pendingCallbacks: [(Any?, String?) -> Void] = []
|
|
58
|
+
|
|
59
|
+
// Map watchId -> weak bridge reference
|
|
60
|
+
private struct WeakBridge { weak var bridge: NativeBridge? }
|
|
61
|
+
private var watchCallbacks: [Int: WeakBridge] = [:]
|
|
62
|
+
private var nextWatchId = 1
|
|
63
|
+
|
|
64
|
+
// MARK: Setup
|
|
65
|
+
|
|
66
|
+
private func ensureManager() {
|
|
67
|
+
guard manager == nil else { return }
|
|
68
|
+
let mgr = CLLocationManager()
|
|
69
|
+
mgr.delegate = self
|
|
70
|
+
mgr.desiredAccuracy = kCLLocationAccuracyBest
|
|
71
|
+
manager = mgr
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// MARK: Public interface (called on main thread)
|
|
75
|
+
|
|
76
|
+
func getCurrentPosition(callback: @escaping (Any?, String?) -> Void) {
|
|
77
|
+
ensureManager()
|
|
78
|
+
guard let manager = manager else {
|
|
79
|
+
callback(nil, "Failed to initialize location manager"); return
|
|
80
|
+
}
|
|
81
|
+
let status = manager.authorizationStatus
|
|
82
|
+
guard status == .authorizedWhenInUse || status == .authorizedAlways else {
|
|
83
|
+
callback(nil, "Location permission not granted"); return
|
|
84
|
+
}
|
|
85
|
+
pendingCallbacks.append(callback)
|
|
86
|
+
manager.requestLocation()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func watchPosition(bridge: NativeBridge?) -> Int {
|
|
90
|
+
ensureManager()
|
|
91
|
+
guard let manager = manager else {
|
|
92
|
+
return -1
|
|
93
|
+
}
|
|
94
|
+
let status = manager.authorizationStatus
|
|
95
|
+
guard status == .authorizedWhenInUse || status == .authorizedAlways else {
|
|
96
|
+
// Return a sentinel; caller will get no updates
|
|
97
|
+
return -1
|
|
98
|
+
}
|
|
99
|
+
let watchId = nextWatchId; nextWatchId += 1
|
|
100
|
+
watchCallbacks[watchId] = WeakBridge(bridge: bridge)
|
|
101
|
+
manager.startUpdatingLocation()
|
|
102
|
+
return watchId
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func clearWatch(_ watchId: Int) {
|
|
106
|
+
watchCallbacks.removeValue(forKey: watchId)
|
|
107
|
+
if watchCallbacks.isEmpty {
|
|
108
|
+
manager?.stopUpdatingLocation()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// MARK: CLLocationManagerDelegate
|
|
113
|
+
// nonisolated required by protocol; we hop to @MainActor via Task.
|
|
114
|
+
|
|
115
|
+
nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
116
|
+
guard let loc = locations.last else { return }
|
|
117
|
+
let payload: [String: Any] = [
|
|
118
|
+
"latitude": loc.coordinate.latitude,
|
|
119
|
+
"longitude": loc.coordinate.longitude,
|
|
120
|
+
"altitude": loc.altitude,
|
|
121
|
+
"accuracy": loc.horizontalAccuracy,
|
|
122
|
+
"altitudeAccuracy": loc.verticalAccuracy,
|
|
123
|
+
"heading": loc.course,
|
|
124
|
+
"speed": loc.speed,
|
|
125
|
+
"timestamp": loc.timestamp.timeIntervalSince1970 * 1000
|
|
126
|
+
]
|
|
127
|
+
Task { @MainActor in
|
|
128
|
+
// Fire one-shot callbacks
|
|
129
|
+
let cbs = self.pendingCallbacks
|
|
130
|
+
self.pendingCallbacks.removeAll()
|
|
131
|
+
for cb in cbs { cb(payload, nil) }
|
|
132
|
+
// Fire watch callbacks
|
|
133
|
+
for (_, wb) in self.watchCallbacks {
|
|
134
|
+
wb.bridge?.dispatchGlobalEvent("location:update", payload: payload)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
|
140
|
+
Task { @MainActor in
|
|
141
|
+
let cbs = self.pendingCallbacks
|
|
142
|
+
self.pendingCallbacks.removeAll()
|
|
143
|
+
for cb in cbs { cb(nil, error.localizedDescription) }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
#endif
|