@thelacanians/vue-native-cli 0.4.3 → 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 +34 -17
- 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,387 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import CoreBluetooth
|
|
4
|
+
|
|
5
|
+
/// Native module for Bluetooth Low Energy (BLE) operations.
|
|
6
|
+
///
|
|
7
|
+
/// Methods:
|
|
8
|
+
/// - startScan(serviceUUIDs?: [String]) — start scanning for peripherals
|
|
9
|
+
/// - stopScan() — stop scanning
|
|
10
|
+
/// - connect(deviceId: String) — connect to a peripheral
|
|
11
|
+
/// - disconnect(deviceId: String) — disconnect from a peripheral
|
|
12
|
+
/// - readCharacteristic(deviceId, serviceUUID, charUUID) — read a characteristic
|
|
13
|
+
/// - writeCharacteristic(deviceId, serviceUUID, charUUID, data) — write a characteristic
|
|
14
|
+
/// - subscribeToCharacteristic(deviceId, serviceUUID, charUUID) — subscribe to notifications
|
|
15
|
+
/// - unsubscribeFromCharacteristic(deviceId, serviceUUID, charUUID)
|
|
16
|
+
/// - getState() — returns current Bluetooth state
|
|
17
|
+
///
|
|
18
|
+
/// Events:
|
|
19
|
+
/// - ble:deviceFound — peripheral discovered
|
|
20
|
+
/// - ble:connected — peripheral connected
|
|
21
|
+
/// - ble:disconnected — peripheral disconnected
|
|
22
|
+
/// - ble:characteristicChanged — characteristic value updated
|
|
23
|
+
/// - ble:stateChanged — Bluetooth state changed
|
|
24
|
+
/// - ble:error — error occurred
|
|
25
|
+
final class BluetoothModule: NativeModule {
|
|
26
|
+
var moduleName: String { "Bluetooth" }
|
|
27
|
+
private weak var bridge: NativeBridge?
|
|
28
|
+
|
|
29
|
+
init(bridge: NativeBridge) { self.bridge = bridge }
|
|
30
|
+
|
|
31
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
32
|
+
let weakBridge = bridge
|
|
33
|
+
switch method {
|
|
34
|
+
case "startScan":
|
|
35
|
+
let serviceUUIDs = (args.first as? [String])
|
|
36
|
+
DispatchQueue.main.async {
|
|
37
|
+
BLEManager.shared.startScan(serviceUUIDs: serviceUUIDs, bridge: weakBridge)
|
|
38
|
+
callback(nil, nil)
|
|
39
|
+
}
|
|
40
|
+
case "stopScan":
|
|
41
|
+
DispatchQueue.main.async {
|
|
42
|
+
BLEManager.shared.stopScan()
|
|
43
|
+
callback(nil, nil)
|
|
44
|
+
}
|
|
45
|
+
case "connect":
|
|
46
|
+
guard let deviceId = args.first as? String else {
|
|
47
|
+
callback(nil, "BluetoothModule: missing deviceId"); return
|
|
48
|
+
}
|
|
49
|
+
DispatchQueue.main.async {
|
|
50
|
+
BLEManager.shared.connect(deviceId: deviceId, bridge: weakBridge, callback: callback)
|
|
51
|
+
}
|
|
52
|
+
case "disconnect":
|
|
53
|
+
guard let deviceId = args.first as? String else {
|
|
54
|
+
callback(nil, "BluetoothModule: missing deviceId"); return
|
|
55
|
+
}
|
|
56
|
+
DispatchQueue.main.async {
|
|
57
|
+
BLEManager.shared.disconnect(deviceId: deviceId, callback: callback)
|
|
58
|
+
}
|
|
59
|
+
case "readCharacteristic":
|
|
60
|
+
guard let deviceId = args[safe: 0] as? String,
|
|
61
|
+
let serviceUUID = args[safe: 1] as? String,
|
|
62
|
+
let charUUID = args[safe: 2] as? String else {
|
|
63
|
+
callback(nil, "BluetoothModule: missing deviceId/serviceUUID/charUUID"); return
|
|
64
|
+
}
|
|
65
|
+
DispatchQueue.main.async {
|
|
66
|
+
BLEManager.shared.readCharacteristic(
|
|
67
|
+
deviceId: deviceId, serviceUUID: serviceUUID, charUUID: charUUID, callback: callback
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
case "writeCharacteristic":
|
|
71
|
+
guard let deviceId = args[safe: 0] as? String,
|
|
72
|
+
let serviceUUID = args[safe: 1] as? String,
|
|
73
|
+
let charUUID = args[safe: 2] as? String,
|
|
74
|
+
let dataBase64 = args[safe: 3] as? String else {
|
|
75
|
+
callback(nil, "BluetoothModule: missing args for writeCharacteristic"); return
|
|
76
|
+
}
|
|
77
|
+
DispatchQueue.main.async {
|
|
78
|
+
BLEManager.shared.writeCharacteristic(
|
|
79
|
+
deviceId: deviceId, serviceUUID: serviceUUID, charUUID: charUUID,
|
|
80
|
+
dataBase64: dataBase64, callback: callback
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
case "subscribeToCharacteristic":
|
|
84
|
+
guard let deviceId = args[safe: 0] as? String,
|
|
85
|
+
let serviceUUID = args[safe: 1] as? String,
|
|
86
|
+
let charUUID = args[safe: 2] as? String else {
|
|
87
|
+
callback(nil, "BluetoothModule: missing args for subscribeToCharacteristic"); return
|
|
88
|
+
}
|
|
89
|
+
DispatchQueue.main.async {
|
|
90
|
+
BLEManager.shared.subscribeToCharacteristic(
|
|
91
|
+
deviceId: deviceId, serviceUUID: serviceUUID, charUUID: charUUID,
|
|
92
|
+
bridge: weakBridge, callback: callback
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
case "unsubscribeFromCharacteristic":
|
|
96
|
+
guard let deviceId = args[safe: 0] as? String,
|
|
97
|
+
let serviceUUID = args[safe: 1] as? String,
|
|
98
|
+
let charUUID = args[safe: 2] as? String else {
|
|
99
|
+
callback(nil, "BluetoothModule: missing args for unsubscribeFromCharacteristic"); return
|
|
100
|
+
}
|
|
101
|
+
DispatchQueue.main.async {
|
|
102
|
+
BLEManager.shared.unsubscribeFromCharacteristic(
|
|
103
|
+
deviceId: deviceId, serviceUUID: serviceUUID, charUUID: charUUID, callback: callback
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
case "getState":
|
|
107
|
+
DispatchQueue.main.async {
|
|
108
|
+
callback(BLEManager.shared.getState(), nil)
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
callback(nil, "BluetoothModule: Unknown method '\(method)'")
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func invokeSync(method: String, args: [Any]) -> Any? { nil }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// MARK: - Safe array subscript
|
|
119
|
+
|
|
120
|
+
private extension Array {
|
|
121
|
+
subscript(safe index: Int) -> Element? {
|
|
122
|
+
indices.contains(index) ? self[index] : nil
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MARK: - BLE Manager
|
|
127
|
+
|
|
128
|
+
@MainActor
|
|
129
|
+
private final class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
|
|
130
|
+
static let shared = BLEManager()
|
|
131
|
+
|
|
132
|
+
private var centralManager: CBCentralManager?
|
|
133
|
+
private var discoveredPeripherals: [String: CBPeripheral] = [:]
|
|
134
|
+
private var connectedPeripherals: [String: CBPeripheral] = [:]
|
|
135
|
+
|
|
136
|
+
private struct WeakBridge { weak var bridge: NativeBridge? }
|
|
137
|
+
private var scanBridge: WeakBridge?
|
|
138
|
+
private var connectCallbacks: [String: (Any?, String?) -> Void] = [:]
|
|
139
|
+
private var readCallbacks: [String: (Any?, String?) -> Void] = [:]
|
|
140
|
+
private var writeCallbacks: [String: (Any?, String?) -> Void] = [:]
|
|
141
|
+
private var subscribeBridges: [String: WeakBridge] = [:]
|
|
142
|
+
|
|
143
|
+
// MARK: Setup
|
|
144
|
+
|
|
145
|
+
private func ensureManager() {
|
|
146
|
+
guard centralManager == nil else { return }
|
|
147
|
+
centralManager = CBCentralManager(delegate: self, queue: nil)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: Public interface
|
|
151
|
+
|
|
152
|
+
func getState() -> String {
|
|
153
|
+
ensureManager()
|
|
154
|
+
return stateString(centralManager?.state ?? .unknown)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func startScan(serviceUUIDs: [String]?, bridge: NativeBridge?) {
|
|
158
|
+
ensureManager()
|
|
159
|
+
scanBridge = WeakBridge(bridge: bridge)
|
|
160
|
+
let uuids = serviceUUIDs?.map { CBUUID(string: $0) }
|
|
161
|
+
centralManager?.scanForPeripherals(withServices: uuids, options: [
|
|
162
|
+
CBCentralManagerScanOptionAllowDuplicatesKey: false
|
|
163
|
+
])
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func stopScan() {
|
|
167
|
+
centralManager?.stopScan()
|
|
168
|
+
scanBridge = nil
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func connect(deviceId: String, bridge: NativeBridge?, callback: @escaping (Any?, String?) -> Void) {
|
|
172
|
+
guard let peripheral = discoveredPeripherals[deviceId] else {
|
|
173
|
+
callback(nil, "Device not found: \(deviceId)"); return
|
|
174
|
+
}
|
|
175
|
+
connectCallbacks[deviceId] = callback
|
|
176
|
+
scanBridge = WeakBridge(bridge: bridge)
|
|
177
|
+
peripheral.delegate = self
|
|
178
|
+
centralManager?.connect(peripheral, options: nil)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func disconnect(deviceId: String, callback: @escaping (Any?, String?) -> Void) {
|
|
182
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
183
|
+
callback(nil, "Device not connected: \(deviceId)"); return
|
|
184
|
+
}
|
|
185
|
+
centralManager?.cancelPeripheralConnection(peripheral)
|
|
186
|
+
callback(nil, nil)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func readCharacteristic(deviceId: String, serviceUUID: String, charUUID: String, callback: @escaping (Any?, String?) -> Void) {
|
|
190
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
191
|
+
callback(nil, "Device not connected: \(deviceId)"); return
|
|
192
|
+
}
|
|
193
|
+
guard let char = findCharacteristic(peripheral: peripheral, serviceUUID: serviceUUID, charUUID: charUUID) else {
|
|
194
|
+
callback(nil, "Characteristic not found: \(charUUID)"); return
|
|
195
|
+
}
|
|
196
|
+
let key = "\(deviceId):\(serviceUUID):\(charUUID)"
|
|
197
|
+
readCallbacks[key] = callback
|
|
198
|
+
peripheral.readValue(for: char)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
func writeCharacteristic(deviceId: String, serviceUUID: String, charUUID: String, dataBase64: String, callback: @escaping (Any?, String?) -> Void) {
|
|
202
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
203
|
+
callback(nil, "Device not connected: \(deviceId)"); return
|
|
204
|
+
}
|
|
205
|
+
guard let char = findCharacteristic(peripheral: peripheral, serviceUUID: serviceUUID, charUUID: charUUID) else {
|
|
206
|
+
callback(nil, "Characteristic not found: \(charUUID)"); return
|
|
207
|
+
}
|
|
208
|
+
guard let data = Data(base64Encoded: dataBase64) else {
|
|
209
|
+
callback(nil, "Invalid base64 data"); return
|
|
210
|
+
}
|
|
211
|
+
let key = "\(deviceId):\(serviceUUID):\(charUUID)"
|
|
212
|
+
writeCallbacks[key] = callback
|
|
213
|
+
let writeType: CBCharacteristicWriteType = char.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse
|
|
214
|
+
peripheral.writeValue(data, for: char, type: writeType)
|
|
215
|
+
if writeType == .withoutResponse {
|
|
216
|
+
writeCallbacks.removeValue(forKey: key)
|
|
217
|
+
callback(nil, nil)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
func subscribeToCharacteristic(deviceId: String, serviceUUID: String, charUUID: String, bridge: NativeBridge?, callback: @escaping (Any?, String?) -> Void) {
|
|
222
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
223
|
+
callback(nil, "Device not connected: \(deviceId)"); return
|
|
224
|
+
}
|
|
225
|
+
guard let char = findCharacteristic(peripheral: peripheral, serviceUUID: serviceUUID, charUUID: charUUID) else {
|
|
226
|
+
callback(nil, "Characteristic not found: \(charUUID)"); return
|
|
227
|
+
}
|
|
228
|
+
let key = "\(deviceId):\(serviceUUID):\(charUUID)"
|
|
229
|
+
subscribeBridges[key] = WeakBridge(bridge: bridge)
|
|
230
|
+
peripheral.setNotifyValue(true, for: char)
|
|
231
|
+
callback(nil, nil)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
func unsubscribeFromCharacteristic(deviceId: String, serviceUUID: String, charUUID: String, callback: @escaping (Any?, String?) -> Void) {
|
|
235
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
236
|
+
callback(nil, "Device not connected: \(deviceId)"); return
|
|
237
|
+
}
|
|
238
|
+
guard let char = findCharacteristic(peripheral: peripheral, serviceUUID: serviceUUID, charUUID: charUUID) else {
|
|
239
|
+
callback(nil, "Characteristic not found: \(charUUID)"); return
|
|
240
|
+
}
|
|
241
|
+
let key = "\(deviceId):\(serviceUUID):\(charUUID)"
|
|
242
|
+
subscribeBridges.removeValue(forKey: key)
|
|
243
|
+
peripheral.setNotifyValue(false, for: char)
|
|
244
|
+
callback(nil, nil)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// MARK: Helpers
|
|
248
|
+
|
|
249
|
+
private func findCharacteristic(peripheral: CBPeripheral, serviceUUID: String, charUUID: String) -> CBCharacteristic? {
|
|
250
|
+
let sUUID = CBUUID(string: serviceUUID)
|
|
251
|
+
let cUUID = CBUUID(string: charUUID)
|
|
252
|
+
guard let service = peripheral.services?.first(where: { $0.uuid == sUUID }) else { return nil }
|
|
253
|
+
return service.characteristics?.first(where: { $0.uuid == cUUID })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private func stateString(_ state: CBManagerState) -> String {
|
|
257
|
+
switch state {
|
|
258
|
+
case .poweredOn: return "poweredOn"
|
|
259
|
+
case .poweredOff: return "poweredOff"
|
|
260
|
+
case .unauthorized: return "unauthorized"
|
|
261
|
+
case .unsupported: return "unsupported"
|
|
262
|
+
case .resetting: return "resetting"
|
|
263
|
+
case .unknown: return "unknown"
|
|
264
|
+
@unknown default: return "unknown"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private func peripheralId(_ peripheral: CBPeripheral) -> String {
|
|
269
|
+
peripheral.identifier.uuidString
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// MARK: CBCentralManagerDelegate
|
|
273
|
+
|
|
274
|
+
nonisolated func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
275
|
+
Task { @MainActor in
|
|
276
|
+
let state = self.stateString(central.state)
|
|
277
|
+
self.scanBridge?.bridge?.dispatchGlobalEvent("ble:stateChanged", payload: ["state": state])
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
nonisolated func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
|
282
|
+
Task { @MainActor in
|
|
283
|
+
let id = self.peripheralId(peripheral)
|
|
284
|
+
self.discoveredPeripherals[id] = peripheral
|
|
285
|
+
let payload: [String: Any] = [
|
|
286
|
+
"id": id,
|
|
287
|
+
"name": peripheral.name ?? "",
|
|
288
|
+
"rssi": RSSI.intValue,
|
|
289
|
+
]
|
|
290
|
+
self.scanBridge?.bridge?.dispatchGlobalEvent("ble:deviceFound", payload: payload)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
nonisolated func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
|
295
|
+
Task { @MainActor in
|
|
296
|
+
let id = self.peripheralId(peripheral)
|
|
297
|
+
self.connectedPeripherals[id] = peripheral
|
|
298
|
+
peripheral.discoverServices(nil)
|
|
299
|
+
let payload: [String: Any] = ["id": id, "name": peripheral.name ?? ""]
|
|
300
|
+
self.scanBridge?.bridge?.dispatchGlobalEvent("ble:connected", payload: payload)
|
|
301
|
+
if let cb = self.connectCallbacks.removeValue(forKey: id) {
|
|
302
|
+
cb(payload, nil)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
nonisolated func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
|
308
|
+
Task { @MainActor in
|
|
309
|
+
let id = self.peripheralId(peripheral)
|
|
310
|
+
self.connectedPeripherals.removeValue(forKey: id)
|
|
311
|
+
let payload: [String: Any] = ["id": id, "name": peripheral.name ?? ""]
|
|
312
|
+
self.scanBridge?.bridge?.dispatchGlobalEvent("ble:disconnected", payload: payload)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
nonisolated func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
|
317
|
+
Task { @MainActor in
|
|
318
|
+
let id = self.peripheralId(peripheral)
|
|
319
|
+
let msg = error?.localizedDescription ?? "Connection failed"
|
|
320
|
+
if let cb = self.connectCallbacks.removeValue(forKey: id) {
|
|
321
|
+
cb(nil, msg)
|
|
322
|
+
}
|
|
323
|
+
self.scanBridge?.bridge?.dispatchGlobalEvent("ble:error", payload: ["message": msg, "deviceId": id])
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// MARK: CBPeripheralDelegate
|
|
328
|
+
|
|
329
|
+
nonisolated func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
|
330
|
+
guard error == nil else { return }
|
|
331
|
+
Task { @MainActor in
|
|
332
|
+
for service in peripheral.services ?? [] {
|
|
333
|
+
peripheral.discoverCharacteristics(nil, for: service)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
nonisolated func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
|
339
|
+
// Characteristics are now available for read/write/subscribe
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
nonisolated func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
343
|
+
Task { @MainActor in
|
|
344
|
+
let id = self.peripheralId(peripheral)
|
|
345
|
+
let serviceUUID = characteristic.service?.uuid.uuidString ?? ""
|
|
346
|
+
let charUUID = characteristic.uuid.uuidString
|
|
347
|
+
let key = "\(id):\(serviceUUID):\(charUUID)"
|
|
348
|
+
let valueBase64 = characteristic.value?.base64EncodedString() ?? ""
|
|
349
|
+
|
|
350
|
+
// One-shot read callback
|
|
351
|
+
if let cb = self.readCallbacks.removeValue(forKey: key) {
|
|
352
|
+
if let err = error {
|
|
353
|
+
cb(nil, err.localizedDescription)
|
|
354
|
+
} else {
|
|
355
|
+
cb(["value": valueBase64, "characteristicUUID": charUUID, "serviceUUID": serviceUUID], nil)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Subscription notification
|
|
360
|
+
if let wb = self.subscribeBridges[key] {
|
|
361
|
+
wb.bridge?.dispatchGlobalEvent("ble:characteristicChanged", payload: [
|
|
362
|
+
"deviceId": id,
|
|
363
|
+
"serviceUUID": serviceUUID,
|
|
364
|
+
"characteristicUUID": charUUID,
|
|
365
|
+
"value": valueBase64,
|
|
366
|
+
])
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
nonisolated func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
372
|
+
Task { @MainActor in
|
|
373
|
+
let id = self.peripheralId(peripheral)
|
|
374
|
+
let serviceUUID = characteristic.service?.uuid.uuidString ?? ""
|
|
375
|
+
let charUUID = characteristic.uuid.uuidString
|
|
376
|
+
let key = "\(id):\(serviceUUID):\(charUUID)"
|
|
377
|
+
if let cb = self.writeCallbacks.removeValue(forKey: key) {
|
|
378
|
+
if let err = error {
|
|
379
|
+
cb(nil, err.localizedDescription)
|
|
380
|
+
} else {
|
|
381
|
+
cb(nil, nil)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
#endif
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import EventKit
|
|
4
|
+
|
|
5
|
+
/// Native module for calendar (EventKit) access.
|
|
6
|
+
///
|
|
7
|
+
/// Methods:
|
|
8
|
+
/// - requestAccess() — request calendar access
|
|
9
|
+
/// - getEvents(startDate, endDate) — fetch events in a date range
|
|
10
|
+
/// - createEvent(title, startDate, endDate, notes?) — create a calendar event
|
|
11
|
+
/// - deleteEvent(eventId) — delete an event
|
|
12
|
+
/// - getCalendars() — list available calendars
|
|
13
|
+
final class CalendarModule: NativeModule {
|
|
14
|
+
var moduleName: String { "Calendar" }
|
|
15
|
+
|
|
16
|
+
private let eventStore = EKEventStore()
|
|
17
|
+
|
|
18
|
+
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
19
|
+
switch method {
|
|
20
|
+
case "requestAccess":
|
|
21
|
+
if #available(iOS 17.0, *) {
|
|
22
|
+
eventStore.requestFullAccessToEvents { granted, error in
|
|
23
|
+
if let error = error {
|
|
24
|
+
callback(nil, error.localizedDescription)
|
|
25
|
+
} else {
|
|
26
|
+
callback(["granted": granted], nil)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
eventStore.requestAccess(to: .event) { granted, error in
|
|
31
|
+
if let error = error {
|
|
32
|
+
callback(nil, error.localizedDescription)
|
|
33
|
+
} else {
|
|
34
|
+
callback(["granted": granted], nil)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "getEvents":
|
|
40
|
+
guard let startMs = args[safe: 0] as? Double,
|
|
41
|
+
let endMs = args[safe: 1] as? Double else {
|
|
42
|
+
callback(nil, "CalendarModule: missing startDate/endDate (milliseconds)"); return
|
|
43
|
+
}
|
|
44
|
+
let startDate = Date(timeIntervalSince1970: startMs / 1000)
|
|
45
|
+
let endDate = Date(timeIntervalSince1970: endMs / 1000)
|
|
46
|
+
let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
|
|
47
|
+
let events = eventStore.events(matching: predicate)
|
|
48
|
+
let result = events.map { eventToDict($0) }
|
|
49
|
+
callback(result, nil)
|
|
50
|
+
|
|
51
|
+
case "createEvent":
|
|
52
|
+
guard let title = args[safe: 0] as? String,
|
|
53
|
+
let startMs = args[safe: 1] as? Double,
|
|
54
|
+
let endMs = args[safe: 2] as? Double else {
|
|
55
|
+
callback(nil, "CalendarModule: missing title/startDate/endDate"); return
|
|
56
|
+
}
|
|
57
|
+
let notes = args[safe: 3] as? String
|
|
58
|
+
let calendarId = args[safe: 4] as? String
|
|
59
|
+
|
|
60
|
+
let event = EKEvent(eventStore: eventStore)
|
|
61
|
+
event.title = title
|
|
62
|
+
event.startDate = Date(timeIntervalSince1970: startMs / 1000)
|
|
63
|
+
event.endDate = Date(timeIntervalSince1970: endMs / 1000)
|
|
64
|
+
event.notes = notes
|
|
65
|
+
|
|
66
|
+
if let calId = calendarId,
|
|
67
|
+
let cal = eventStore.calendar(withIdentifier: calId) {
|
|
68
|
+
event.calendar = cal
|
|
69
|
+
} else {
|
|
70
|
+
event.calendar = eventStore.defaultCalendarForNewEvents
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
do {
|
|
74
|
+
try eventStore.save(event, span: .thisEvent)
|
|
75
|
+
callback(["eventId": event.eventIdentifier ?? ""], nil)
|
|
76
|
+
} catch {
|
|
77
|
+
callback(nil, error.localizedDescription)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
case "deleteEvent":
|
|
81
|
+
guard let eventId = args[safe: 0] as? String else {
|
|
82
|
+
callback(nil, "CalendarModule: missing eventId"); return
|
|
83
|
+
}
|
|
84
|
+
guard let event = eventStore.event(withIdentifier: eventId) else {
|
|
85
|
+
callback(nil, "Event not found: \(eventId)"); return
|
|
86
|
+
}
|
|
87
|
+
do {
|
|
88
|
+
try eventStore.remove(event, span: .thisEvent)
|
|
89
|
+
callback(nil, nil)
|
|
90
|
+
} catch {
|
|
91
|
+
callback(nil, error.localizedDescription)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case "getCalendars":
|
|
95
|
+
let calendars = eventStore.calendars(for: .event)
|
|
96
|
+
let result = calendars.map { calendarToDict($0) }
|
|
97
|
+
callback(result, nil)
|
|
98
|
+
|
|
99
|
+
default:
|
|
100
|
+
callback(nil, "CalendarModule: Unknown method '\(method)'")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func invokeSync(method: String, args: [Any]) -> Any? { nil }
|
|
105
|
+
|
|
106
|
+
// MARK: - Helpers
|
|
107
|
+
|
|
108
|
+
private func eventToDict(_ event: EKEvent) -> [String: Any] {
|
|
109
|
+
var dict: [String: Any] = [
|
|
110
|
+
"id": event.eventIdentifier ?? "",
|
|
111
|
+
"title": event.title ?? "",
|
|
112
|
+
"startDate": (event.startDate?.timeIntervalSince1970 ?? 0) * 1000,
|
|
113
|
+
"endDate": (event.endDate?.timeIntervalSince1970 ?? 0) * 1000,
|
|
114
|
+
"isAllDay": event.isAllDay,
|
|
115
|
+
"calendarId": event.calendar?.calendarIdentifier ?? "",
|
|
116
|
+
]
|
|
117
|
+
if let notes = event.notes { dict["notes"] = notes }
|
|
118
|
+
if let location = event.location { dict["location"] = location }
|
|
119
|
+
return dict
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private func calendarToDict(_ calendar: EKCalendar) -> [String: Any] {
|
|
123
|
+
return [
|
|
124
|
+
"id": calendar.calendarIdentifier,
|
|
125
|
+
"title": calendar.title,
|
|
126
|
+
"color": hexString(from: calendar.cgColor),
|
|
127
|
+
"isImmutable": calendar.isImmutable,
|
|
128
|
+
"type": calendarTypeString(calendar.type),
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private func calendarTypeString(_ type: EKCalendarType) -> String {
|
|
133
|
+
switch type {
|
|
134
|
+
case .local: return "local"
|
|
135
|
+
case .calDAV: return "calDAV"
|
|
136
|
+
case .exchange: return "exchange"
|
|
137
|
+
case .subscription: return "subscription"
|
|
138
|
+
case .birthday: return "birthday"
|
|
139
|
+
@unknown default: return "unknown"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private func hexString(from cgColor: CGColor?) -> String {
|
|
144
|
+
guard let color = cgColor,
|
|
145
|
+
let components = color.components,
|
|
146
|
+
components.count >= 3 else { return "#000000" }
|
|
147
|
+
let r = Int(components[0] * 255)
|
|
148
|
+
let g = Int(components[1] * 255)
|
|
149
|
+
let b = Int(components[2] * 255)
|
|
150
|
+
return String(format: "#%02X%02X%02X", r, g, b)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// MARK: - Safe array subscript (shared)
|
|
155
|
+
|
|
156
|
+
private extension Array {
|
|
157
|
+
subscript(safe index: Int) -> Element? {
|
|
158
|
+
indices.contains(index) ? self[index] : nil
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
#endif
|