@thelacanians/vue-native-cli 0.4.14 → 0.6.0

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.
Files changed (123) hide show
  1. package/dist/cli.js +789 -200
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +156 -5
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
  6. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  7. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/EventThrottle.kt +57 -0
  8. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  9. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  13. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  14. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  15. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  16. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  17. package/native/android/gradle.properties +1 -0
  18. package/native/android/gradlew +1 -1
  19. package/native/ios/VueNativeCore/Package.swift +1 -1
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +80 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +244 -112
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
  23. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
  24. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
  25. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  26. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  27. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  28. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  29. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  30. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  31. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  32. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  33. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  34. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  35. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  36. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  94. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  95. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  96. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  97. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  98. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  99. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  100. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  101. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  102. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  103. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  104. package/native/shared/VueNativeShared/Package.swift +14 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  115. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  116. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  117. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  118. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  119. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  120. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  121. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  122. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  123. package/package.json +8 -2
@@ -0,0 +1,359 @@
1
+ import Foundation
2
+ import AVFoundation
3
+
4
+ /// Native module for audio playback and recording.
5
+ /// Uses AVFoundation which is available on both iOS and macOS.
6
+ ///
7
+ /// Methods:
8
+ /// - play(uri: String, options?: Object) -- play audio from URI
9
+ /// - pause() -- pause playback
10
+ /// - resume() -- resume playback
11
+ /// - stop() -- stop playback and release player
12
+ /// - seek(position: Number) -- seek to position in seconds
13
+ /// - setVolume(volume: Number) -- set volume 0.0-1.0
14
+ /// - startRecording(options?: Object) -- start audio recording
15
+ /// - stopRecording() -- stop recording, returns { uri, duration }
16
+ /// - pauseRecording() -- pause recording
17
+ /// - resumeRecording() -- resume recording
18
+ /// - getStatus() -- returns current playback status
19
+ ///
20
+ /// Events (via eventDispatcher.dispatchGlobalEvent):
21
+ /// - audio:progress { currentTime, duration }
22
+ /// - audio:complete {}
23
+ /// - audio:error { message }
24
+ public final class AudioModule: NSObject, NativeModule {
25
+ public let moduleName = "Audio"
26
+
27
+ private var player: AVAudioPlayer?
28
+ private var recorder: AVAudioRecorder?
29
+ private var progressTimer: Timer?
30
+ private var isPlaying = false
31
+ private weak var eventDispatcher: NativeEventDispatcher?
32
+
33
+ // MARK: - Delegate to forward completion events
34
+ private var playerDelegate: AudioPlayerDelegateImpl?
35
+
36
+ public init(eventDispatcher: NativeEventDispatcher) {
37
+ self.eventDispatcher = eventDispatcher
38
+ super.init()
39
+ }
40
+
41
+ public func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
42
+ switch method {
43
+ case "play":
44
+ let uri = args.first as? String ?? ""
45
+ let options = (args.count > 1 ? args[1] as? [String: Any] : nil) ?? [:]
46
+ play(uri: uri, options: options, callback: callback)
47
+
48
+ case "pause":
49
+ pause(callback: callback)
50
+
51
+ case "resume":
52
+ resume(callback: callback)
53
+
54
+ case "stop":
55
+ stop(callback: callback)
56
+
57
+ case "seek":
58
+ let position = (args.first as? Double) ?? (args.first as? Int).map(Double.init) ?? 0
59
+ seek(position: position, callback: callback)
60
+
61
+ case "setVolume":
62
+ let volume = (args.first as? Double) ?? (args.first as? Int).map(Double.init) ?? 1.0
63
+ setVolume(Float(volume), callback: callback)
64
+
65
+ case "startRecording":
66
+ let options = args.first as? [String: Any] ?? [:]
67
+ startRecording(options: options, callback: callback)
68
+
69
+ case "stopRecording":
70
+ stopRecording(callback: callback)
71
+
72
+ case "pauseRecording":
73
+ pauseRecording(callback: callback)
74
+
75
+ case "resumeRecording":
76
+ resumeRecording(callback: callback)
77
+
78
+ case "getStatus":
79
+ getStatus(callback: callback)
80
+
81
+ default:
82
+ callback(nil, "AudioModule: Unknown method '\(method)'")
83
+ }
84
+ }
85
+
86
+ // MARK: - Playback
87
+
88
+ private func play(uri: String, options: [String: Any], callback: @escaping (Any?, String?) -> Void) {
89
+ DispatchQueue.main.async { [weak self] in
90
+ guard let self = self else { return }
91
+
92
+ // Stop any existing playback
93
+ self.stopProgressReporting()
94
+ self.player?.stop()
95
+
96
+ guard let url = URL(string: uri) else {
97
+ callback(nil, "Invalid audio URI: \(uri)")
98
+ return
99
+ }
100
+
101
+ // Check if it's a remote URL — download first
102
+ if url.scheme == "http" || url.scheme == "https" {
103
+ self.downloadAndPlay(url: url, options: options, callback: callback)
104
+ } else {
105
+ self.playLocal(url: url, options: options, callback: callback)
106
+ }
107
+ }
108
+ }
109
+
110
+ private func downloadAndPlay(url: URL, options: [String: Any], callback: @escaping (Any?, String?) -> Void) {
111
+ URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
112
+ DispatchQueue.main.async {
113
+ guard let self = self else { return }
114
+ if let error = error {
115
+ callback(nil, "Failed to download audio: \(error.localizedDescription)")
116
+ self.eventDispatcher?.dispatchGlobalEvent("audio:error", payload: ["message": error.localizedDescription])
117
+ return
118
+ }
119
+ guard let data = data else {
120
+ callback(nil, "No audio data received")
121
+ return
122
+ }
123
+ do {
124
+ let player = try AVAudioPlayer(data: data)
125
+ self.setupPlayer(player, options: options, callback: callback)
126
+ } catch {
127
+ callback(nil, "Failed to initialize player: \(error.localizedDescription)")
128
+ }
129
+ }
130
+ }.resume()
131
+ }
132
+
133
+ private func playLocal(url: URL, options: [String: Any], callback: @escaping (Any?, String?) -> Void) {
134
+ do {
135
+ let player = try AVAudioPlayer(contentsOf: url)
136
+ setupPlayer(player, options: options, callback: callback)
137
+ } catch {
138
+ callback(nil, "Failed to play audio: \(error.localizedDescription)")
139
+ }
140
+ }
141
+
142
+ private func setupPlayer(_ player: AVAudioPlayer, options: [String: Any], callback: @escaping (Any?, String?) -> Void) {
143
+ let volume = Float(options["volume"] as? Double ?? 1.0)
144
+ let loop = options["loop"] as? Bool ?? false
145
+
146
+ player.volume = volume
147
+ player.numberOfLoops = loop ? -1 : 0
148
+
149
+ let delegate = AudioPlayerDelegateImpl { [weak self] successfully in
150
+ guard let self = self else { return }
151
+ self.isPlaying = false
152
+ self.stopProgressReporting()
153
+ DispatchQueue.main.async { [weak self] in
154
+ self?.eventDispatcher?.dispatchGlobalEvent("audio:complete", payload: [:])
155
+ }
156
+ }
157
+ player.delegate = delegate
158
+ self.playerDelegate = delegate
159
+ self.player = player
160
+
161
+ player.prepareToPlay()
162
+ player.play()
163
+ self.isPlaying = true
164
+ self.startProgressReporting()
165
+
166
+ callback([
167
+ "duration": player.duration,
168
+ "currentTime": 0.0,
169
+ ], nil)
170
+ }
171
+
172
+ private func pause(callback: @escaping (Any?, String?) -> Void) {
173
+ DispatchQueue.main.async { [weak self] in
174
+ self?.player?.pause()
175
+ self?.isPlaying = false
176
+ self?.stopProgressReporting()
177
+ callback(nil, nil)
178
+ }
179
+ }
180
+
181
+ private func resume(callback: @escaping (Any?, String?) -> Void) {
182
+ DispatchQueue.main.async { [weak self] in
183
+ guard let self = self else { return }
184
+ self.player?.play()
185
+ self.isPlaying = true
186
+ self.startProgressReporting()
187
+ callback(nil, nil)
188
+ }
189
+ }
190
+
191
+ private func stop(callback: @escaping (Any?, String?) -> Void) {
192
+ DispatchQueue.main.async { [weak self] in
193
+ guard let self = self else { return }
194
+ self.player?.stop()
195
+ self.player = nil
196
+ self.playerDelegate = nil
197
+ self.isPlaying = false
198
+ self.stopProgressReporting()
199
+ callback(nil, nil)
200
+ }
201
+ }
202
+
203
+ private func seek(position: Double, callback: @escaping (Any?, String?) -> Void) {
204
+ DispatchQueue.main.async { [weak self] in
205
+ self?.player?.currentTime = position
206
+ callback(nil, nil)
207
+ }
208
+ }
209
+
210
+ private func setVolume(_ volume: Float, callback: @escaping (Any?, String?) -> Void) {
211
+ DispatchQueue.main.async { [weak self] in
212
+ self?.player?.volume = max(0, min(1, volume))
213
+ callback(nil, nil)
214
+ }
215
+ }
216
+
217
+ // MARK: - Progress Reporting (Timer-based, cross-platform)
218
+
219
+ private func startProgressReporting() {
220
+ stopProgressReporting()
221
+ // Report at ~4 Hz using a Timer (cross-platform, no CADisplayLink dependency)
222
+ progressTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] _ in
223
+ self?.reportProgress()
224
+ }
225
+ RunLoop.main.add(progressTimer!, forMode: .common)
226
+ }
227
+
228
+ private func stopProgressReporting() {
229
+ progressTimer?.invalidate()
230
+ progressTimer = nil
231
+ }
232
+
233
+ private func reportProgress() {
234
+ guard player != nil, isPlaying else { return }
235
+ DispatchQueue.main.async { [weak self] in
236
+ guard let self = self, let player = self.player else { return }
237
+ self.eventDispatcher?.dispatchGlobalEvent("audio:progress", payload: [
238
+ "currentTime": player.currentTime,
239
+ "duration": player.duration,
240
+ ])
241
+ }
242
+ }
243
+
244
+ // MARK: - Recording
245
+
246
+ private func startRecording(options: [String: Any], callback: @escaping (Any?, String?) -> Void) {
247
+ DispatchQueue.main.async { [weak self] in
248
+ guard let self = self else { return }
249
+
250
+ let quality = options["quality"] as? String ?? "medium"
251
+ let format = options["format"] as? String ?? "m4a"
252
+
253
+ let ext = format == "wav" ? "wav" : "m4a"
254
+ let url = FileManager.default.temporaryDirectory
255
+ .appendingPathComponent(UUID().uuidString + ".\(ext)")
256
+
257
+ var settings: [String: Any] = [:]
258
+ if format == "wav" {
259
+ settings = [
260
+ AVFormatIDKey: Int(kAudioFormatLinearPCM),
261
+ AVSampleRateKey: quality == "high" ? 44100.0 : 22050.0,
262
+ AVNumberOfChannelsKey: 1,
263
+ AVLinearPCMBitDepthKey: 16,
264
+ AVLinearPCMIsFloatKey: false,
265
+ ]
266
+ } else {
267
+ let sampleRate: Double
268
+ let bitRate: Int
269
+ switch quality {
270
+ case "low": sampleRate = 22050; bitRate = 32000
271
+ case "high": sampleRate = 44100; bitRate = 128000
272
+ default: sampleRate = 44100; bitRate = 64000
273
+ }
274
+ settings = [
275
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
276
+ AVSampleRateKey: sampleRate,
277
+ AVNumberOfChannelsKey: 1,
278
+ AVEncoderAudioQualityKey: AVAudioQuality.medium.rawValue,
279
+ AVEncoderBitRateKey: bitRate,
280
+ ]
281
+ }
282
+
283
+ do {
284
+ let recorder = try AVAudioRecorder(url: url, settings: settings)
285
+ recorder.prepareToRecord()
286
+ recorder.record()
287
+ self.recorder = recorder
288
+ callback(["uri": url.absoluteString], nil)
289
+ } catch {
290
+ callback(nil, "Failed to start recording: \(error.localizedDescription)")
291
+ }
292
+ }
293
+ }
294
+
295
+ private func stopRecording(callback: @escaping (Any?, String?) -> Void) {
296
+ DispatchQueue.main.async { [weak self] in
297
+ guard let self = self, let recorder = self.recorder else {
298
+ callback(nil, "No active recording")
299
+ return
300
+ }
301
+ let duration = recorder.currentTime
302
+ let uri = recorder.url.absoluteString
303
+ recorder.stop()
304
+ self.recorder = nil
305
+ callback(["uri": uri, "duration": duration], nil)
306
+ }
307
+ }
308
+
309
+ private func pauseRecording(callback: @escaping (Any?, String?) -> Void) {
310
+ DispatchQueue.main.async { [weak self] in
311
+ self?.recorder?.pause()
312
+ callback(nil, nil)
313
+ }
314
+ }
315
+
316
+ private func resumeRecording(callback: @escaping (Any?, String?) -> Void) {
317
+ DispatchQueue.main.async { [weak self] in
318
+ self?.recorder?.record()
319
+ callback(nil, nil)
320
+ }
321
+ }
322
+
323
+ // MARK: - Status
324
+
325
+ private func getStatus(callback: @escaping (Any?, String?) -> Void) {
326
+ DispatchQueue.main.async { [weak self] in
327
+ guard let self = self else { callback(nil, nil); return }
328
+ var status: [String: Any] = [
329
+ "isPlaying": self.isPlaying,
330
+ "isRecording": self.recorder?.isRecording ?? false,
331
+ ]
332
+ if let player = self.player {
333
+ status["currentTime"] = player.currentTime
334
+ status["duration"] = player.duration
335
+ status["volume"] = player.volume
336
+ }
337
+ callback(status, nil)
338
+ }
339
+ }
340
+ }
341
+
342
+ // MARK: - AVAudioPlayerDelegate wrapper
343
+
344
+ private final class AudioPlayerDelegateImpl: NSObject, AVAudioPlayerDelegate {
345
+ private let onComplete: (Bool) -> Void
346
+
347
+ init(onComplete: @escaping (Bool) -> Void) {
348
+ self.onComplete = onComplete
349
+ }
350
+
351
+ func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
352
+ onComplete(flag)
353
+ }
354
+
355
+ func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
356
+ // Treat decode error as completion failure
357
+ onComplete(false)
358
+ }
359
+ }
@@ -0,0 +1,259 @@
1
+ import Foundation
2
+ import SQLite3
3
+
4
+ /// Native module for SQLite database access.
5
+ /// Uses the sqlite3 C API (built into both iOS and macOS, no external dependencies).
6
+ /// Supports multiple named databases, parameterized queries, and transactions.
7
+ public final class DatabaseModule: NativeModule {
8
+ public let moduleName = "Database"
9
+
10
+ /// Open database handles keyed by database name.
11
+ private var databases: [String: OpaquePointer] = [:]
12
+
13
+ /// Directory for database files.
14
+ private var dbDirectory: URL {
15
+ let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
16
+ .appendingPathComponent("databases", isDirectory: true)
17
+ try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
18
+ return dir
19
+ }
20
+
21
+ public init() {}
22
+
23
+ public func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
24
+ switch method {
25
+ case "open":
26
+ let name = args.first as? String ?? "default"
27
+ open(name: name, callback: callback)
28
+ case "close":
29
+ let name = args.first as? String ?? "default"
30
+ close(name: name, callback: callback)
31
+ case "execute":
32
+ let name = args.count > 0 ? (args[0] as? String ?? "default") : "default"
33
+ let sql = args.count > 1 ? (args[1] as? String ?? "") : ""
34
+ let params = args.count > 2 ? (args[2] as? [Any] ?? []) : []
35
+ execute(name: name, sql: sql, params: params, callback: callback)
36
+ case "query":
37
+ let name = args.count > 0 ? (args[0] as? String ?? "default") : "default"
38
+ let sql = args.count > 1 ? (args[1] as? String ?? "") : ""
39
+ let params = args.count > 2 ? (args[2] as? [Any] ?? []) : []
40
+ query(name: name, sql: sql, params: params, callback: callback)
41
+ case "executeTransaction":
42
+ let name = args.count > 0 ? (args[0] as? String ?? "default") : "default"
43
+ let statements = args.count > 1 ? (args[1] as? [[String: Any]] ?? []) : []
44
+ executeTransaction(name: name, statements: statements, callback: callback)
45
+ default:
46
+ callback(nil, "DatabaseModule: unknown method '\(method)'")
47
+ }
48
+ }
49
+
50
+ // MARK: - Open / Close
51
+
52
+ private func open(name: String, callback: @escaping (Any?, String?) -> Void) {
53
+ if databases[name] != nil {
54
+ callback(true, nil)
55
+ return
56
+ }
57
+
58
+ let path = dbDirectory.appendingPathComponent("\(name).sqlite").path
59
+ var db: OpaquePointer?
60
+
61
+ let flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX
62
+ let result = sqlite3_open_v2(path, &db, flags, nil)
63
+ if result == SQLITE_OK, let db = db {
64
+ // Enable WAL mode for better concurrent read/write performance
65
+ sqlite3_exec(db, "PRAGMA journal_mode=WAL", nil, nil, nil)
66
+ databases[name] = db
67
+ callback(true, nil)
68
+ } else {
69
+ let errorMsg = db != nil ? String(cString: sqlite3_errmsg(db)) : "Unknown error"
70
+ if db != nil { sqlite3_close(db) }
71
+ callback(nil, "Failed to open database '\(name)': \(errorMsg)")
72
+ }
73
+ }
74
+
75
+ private func close(name: String, callback: @escaping (Any?, String?) -> Void) {
76
+ guard let db = databases.removeValue(forKey: name) else {
77
+ callback(nil, nil)
78
+ return
79
+ }
80
+ sqlite3_close(db)
81
+ callback(nil, nil)
82
+ }
83
+
84
+ // MARK: - Execute (INSERT, UPDATE, DELETE, CREATE TABLE, etc.)
85
+
86
+ private func execute(name: String, sql: String, params: [Any], callback: @escaping (Any?, String?) -> Void) {
87
+ guard let db = getOrOpen(name: name, callback: callback) else { return }
88
+
89
+ var stmt: OpaquePointer?
90
+ guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {
91
+ let err = String(cString: sqlite3_errmsg(db))
92
+ callback(nil, "SQL prepare error: \(err)")
93
+ return
94
+ }
95
+ defer { sqlite3_finalize(stmt) }
96
+
97
+ bindParams(stmt: stmt!, params: params)
98
+
99
+ let stepResult = sqlite3_step(stmt)
100
+ if stepResult == SQLITE_DONE || stepResult == SQLITE_ROW {
101
+ let rowsAffected = sqlite3_changes(db)
102
+ let lastInsertId = sqlite3_last_insert_rowid(db)
103
+ var result: [String: Any] = ["rowsAffected": Int(rowsAffected)]
104
+ if lastInsertId > 0 {
105
+ result["insertId"] = Int(lastInsertId)
106
+ }
107
+ callback(result, nil)
108
+ } else {
109
+ let err = String(cString: sqlite3_errmsg(db))
110
+ callback(nil, "SQL execute error: \(err)")
111
+ }
112
+ }
113
+
114
+ // MARK: - Query (SELECT)
115
+
116
+ private func query(name: String, sql: String, params: [Any], callback: @escaping (Any?, String?) -> Void) {
117
+ guard let db = getOrOpen(name: name, callback: callback) else { return }
118
+
119
+ var stmt: OpaquePointer?
120
+ guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {
121
+ let err = String(cString: sqlite3_errmsg(db))
122
+ callback(nil, "SQL prepare error: \(err)")
123
+ return
124
+ }
125
+ defer { sqlite3_finalize(stmt) }
126
+
127
+ bindParams(stmt: stmt!, params: params)
128
+
129
+ var rows: [[String: Any]] = []
130
+ let columnCount = sqlite3_column_count(stmt)
131
+
132
+ while sqlite3_step(stmt) == SQLITE_ROW {
133
+ var row: [String: Any] = [:]
134
+ for i in 0..<columnCount {
135
+ let colName = String(cString: sqlite3_column_name(stmt, i))
136
+ row[colName] = columnValue(stmt: stmt!, index: i)
137
+ }
138
+ rows.append(row)
139
+ }
140
+
141
+ callback(rows, nil)
142
+ }
143
+
144
+ // MARK: - Transaction
145
+
146
+ private func executeTransaction(name: String, statements: [[String: Any]], callback: @escaping (Any?, String?) -> Void) {
147
+ guard let db = getOrOpen(name: name, callback: callback) else { return }
148
+
149
+ sqlite3_exec(db, "BEGIN TRANSACTION", nil, nil, nil)
150
+
151
+ var results: [[String: Any]] = []
152
+
153
+ for stmtData in statements {
154
+ let sql = stmtData["sql"] as? String ?? ""
155
+ let params = stmtData["params"] as? [Any] ?? []
156
+
157
+ var stmt: OpaquePointer?
158
+ guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {
159
+ let err = String(cString: sqlite3_errmsg(db))
160
+ sqlite3_exec(db, "ROLLBACK", nil, nil, nil)
161
+ callback(nil, "Transaction SQL prepare error: \(err)")
162
+ return
163
+ }
164
+
165
+ bindParams(stmt: stmt!, params: params)
166
+
167
+ let stepResult = sqlite3_step(stmt)
168
+ sqlite3_finalize(stmt)
169
+
170
+ if stepResult == SQLITE_DONE || stepResult == SQLITE_ROW {
171
+ let rowsAffected = sqlite3_changes(db)
172
+ let lastInsertId = sqlite3_last_insert_rowid(db)
173
+ var result: [String: Any] = ["rowsAffected": Int(rowsAffected)]
174
+ if lastInsertId > 0 {
175
+ result["insertId"] = Int(lastInsertId)
176
+ }
177
+ results.append(result)
178
+ } else {
179
+ let err = String(cString: sqlite3_errmsg(db))
180
+ sqlite3_exec(db, "ROLLBACK", nil, nil, nil)
181
+ callback(nil, "Transaction execute error: \(err)")
182
+ return
183
+ }
184
+ }
185
+
186
+ sqlite3_exec(db, "COMMIT", nil, nil, nil)
187
+ callback(results, nil)
188
+ }
189
+
190
+ // MARK: - Helpers
191
+
192
+ /// Get an existing database handle or auto-open it.
193
+ private func getOrOpen(name: String, callback: @escaping (Any?, String?) -> Void) -> OpaquePointer? {
194
+ if let db = databases[name] { return db }
195
+
196
+ // Auto-open
197
+ let path = dbDirectory.appendingPathComponent("\(name).sqlite").path
198
+ var db: OpaquePointer?
199
+ let flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX
200
+ let result = sqlite3_open_v2(path, &db, flags, nil)
201
+ if result == SQLITE_OK, let db = db {
202
+ sqlite3_exec(db, "PRAGMA journal_mode=WAL", nil, nil, nil)
203
+ databases[name] = db
204
+ return db
205
+ } else {
206
+ let errorMsg = db != nil ? String(cString: sqlite3_errmsg(db)) : "Unknown error"
207
+ if db != nil { sqlite3_close(db) }
208
+ callback(nil, "Failed to auto-open database '\(name)': \(errorMsg)")
209
+ return nil
210
+ }
211
+ }
212
+
213
+ /// Bind parameter array to a prepared statement. Supports String, Int, Double, Bool, nil/NSNull.
214
+ private func bindParams(stmt: OpaquePointer, params: [Any]) {
215
+ for (i, param) in params.enumerated() {
216
+ let idx = Int32(i + 1)
217
+ if param is NSNull {
218
+ sqlite3_bind_null(stmt, idx)
219
+ } else if let s = param as? String {
220
+ sqlite3_bind_text(stmt, idx, (s as NSString).utf8String, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
221
+ } else if let n = param as? Int {
222
+ sqlite3_bind_int64(stmt, idx, Int64(n))
223
+ } else if let d = param as? Double {
224
+ sqlite3_bind_double(stmt, idx, d)
225
+ } else if let b = param as? Bool {
226
+ sqlite3_bind_int(stmt, idx, b ? 1 : 0)
227
+ } else {
228
+ // Fallback: bind as text
229
+ let str = "\(param)"
230
+ sqlite3_bind_text(stmt, idx, (str as NSString).utf8String, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
231
+ }
232
+ }
233
+ }
234
+
235
+ /// Extract a column value from the current row of a statement.
236
+ private func columnValue(stmt: OpaquePointer, index: Int32) -> Any {
237
+ let type = sqlite3_column_type(stmt, index)
238
+ switch type {
239
+ case SQLITE_INTEGER:
240
+ return Int(sqlite3_column_int64(stmt, index))
241
+ case SQLITE_FLOAT:
242
+ return sqlite3_column_double(stmt, index)
243
+ case SQLITE_TEXT:
244
+ return String(cString: sqlite3_column_text(stmt, index))
245
+ case SQLITE_BLOB:
246
+ // Return blob as base64 string
247
+ if let data = sqlite3_column_blob(stmt, index) {
248
+ let bytes = sqlite3_column_bytes(stmt, index)
249
+ let d = Data(bytes: data, count: Int(bytes))
250
+ return d.base64EncodedString()
251
+ }
252
+ return NSNull()
253
+ case SQLITE_NULL:
254
+ return NSNull()
255
+ default:
256
+ return NSNull()
257
+ }
258
+ }
259
+ }