react-native-flic2 2.0.0-beta.1 → 2.0.0-beta.11

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.
@@ -1,103 +0,0 @@
1
- package com.flic2
2
-
3
- import com.facebook.react.bridge.Arguments
4
- import com.facebook.react.bridge.WritableArray
5
- import com.facebook.react.bridge.WritableMap
6
- import io.flic.flic2libandroid.Flic2Button
7
-
8
- object Flic2Converter {
9
-
10
- fun buttonToMap(button: Flic2Button): WritableMap {
11
- return Arguments.createMap().apply {
12
- putString("uuid", button.uuid)
13
- putString("identifier", button.uuid) // Android uses UUID as identifier
14
- putString("name", button.getName() ?: "")
15
- putString("nickname", button.getName() ?: "")
16
- putString("bluetoothAddress", button.getBdAddr() ?: "")
17
- putString("serialNumber", button.getSerialNumber() ?: "")
18
-
19
- // Use connection state constants instead of enum
20
- val connState = button.getConnectionState()
21
- putInt("state", connectionStateToInt(connState))
22
- putString("stateName", connectionStateToString(connState))
23
-
24
- // iOS-only: Return defaults for trigger/latency mode (not supported in Android v1.1.0+)
25
- putInt("triggerMode", 0)
26
- putString("triggerModeName", "")
27
- putInt("latencyMode", 0)
28
- putString("latencyModeName", "")
29
-
30
- putInt("pressCount", button.getPressCount())
31
- putInt("firmwareRevision", button.getFirmwareVersion())
32
-
33
- // Check if ready by comparing connection state
34
- val isReady = connState == Flic2Button.CONNECTION_STATE_CONNECTED_READY
35
- putBoolean("isReady", isReady)
36
-
37
- // Get battery level from BatteryLevel object
38
- val batteryLevel = button.getLastKnownBatteryLevel()
39
- putDouble("batteryVoltage", batteryLevel?.voltage?.toDouble() ?: 0.0)
40
-
41
- putBoolean("isUnpaired", button.isUnpaired())
42
- }
43
- }
44
-
45
- fun buttonsToArray(buttons: List<Flic2Button>): WritableArray {
46
- return Arguments.createArray().apply {
47
- buttons.forEach { button ->
48
- pushMap(buttonToMap(button))
49
- }
50
- }
51
- }
52
-
53
- private fun connectionStateToInt(state: Int): Int {
54
- return when (state) {
55
- Flic2Button.CONNECTION_STATE_DISCONNECTED -> 0
56
- Flic2Button.CONNECTION_STATE_CONNECTING -> 1
57
- Flic2Button.CONNECTION_STATE_CONNECTED_STARTING -> 2
58
- Flic2Button.CONNECTION_STATE_CONNECTED_READY -> 3
59
- else -> 0
60
- }
61
- }
62
-
63
- private fun connectionStateToString(state: Int): String {
64
- return when (state) {
65
- Flic2Button.CONNECTION_STATE_DISCONNECTED -> "disconnected"
66
- Flic2Button.CONNECTION_STATE_CONNECTING -> "connecting"
67
- Flic2Button.CONNECTION_STATE_CONNECTED_STARTING -> "connected"
68
- Flic2Button.CONNECTION_STATE_CONNECTED_READY -> "connected"
69
- else -> "disconnected"
70
- }
71
- }
72
-
73
- // Trigger mode and latency mode are iOS-only features (removed from Android library v1.1.0+)
74
-
75
- fun scanResultToString(result: Int): String {
76
- return when (result) {
77
- 0 -> "success"
78
- 1 -> "alreadyRunning"
79
- 2 -> "bluetoothNotActivated"
80
- 3 -> "unknown"
81
- 4 -> "noPublicButtonDiscovered"
82
- 5 -> "alreadyConnectedToAnotherDevice"
83
- 6 -> "connectionTimeout"
84
- 7 -> "invalidVerifier"
85
- 8 -> "blePairingFailedPreviousPairingAlreadyExisting"
86
- 9 -> "blePairingFailedUserCanceled"
87
- 10 -> "blePairingFailedUnknownReason"
88
- 11 -> "appCredentialsDontMatch"
89
- 12 -> "userCanceled"
90
- 13 -> "invalidBluetoothAddress"
91
- 14 -> "genuineCheckFailed"
92
- 15 -> "tooManyApps"
93
- 16 -> "couldNotSetBluetoothNotify"
94
- 17 -> "couldNotDiscoverBluetoothServices"
95
- 18 -> "buttonDisconnectedDuringVerification"
96
- 19 -> "failedToEstablish"
97
- 20 -> "connectionLimitReached"
98
- 21 -> "notInPublicMode"
99
- else -> "unknown"
100
- }
101
- }
102
- }
103
-
@@ -1,481 +0,0 @@
1
- package com.flic2
2
-
3
- import android.content.ComponentName
4
- import android.content.Context
5
- import android.content.Intent
6
- import android.content.ServiceConnection
7
- import android.os.Build
8
- import android.os.IBinder
9
- import android.util.Log
10
- import com.facebook.react.bridge.Arguments
11
- import com.facebook.react.bridge.Promise
12
- import com.facebook.react.bridge.ReactApplicationContext
13
- import com.facebook.react.bridge.WritableMap
14
- import com.facebook.react.module.annotations.ReactModule
15
- import io.flic.flic2libandroid.Flic2Button
16
- import io.flic.flic2libandroid.Flic2Manager
17
- import io.flic.flic2libandroid.Flic2ScanCallback
18
- import kotlinx.coroutines.CoroutineScope
19
- import kotlinx.coroutines.Dispatchers
20
- import kotlinx.coroutines.Job
21
- import kotlinx.coroutines.TimeoutCancellationException
22
- import kotlinx.coroutines.cancel
23
- import kotlinx.coroutines.launch
24
- import kotlinx.coroutines.suspendCancellableCoroutine
25
- import kotlinx.coroutines.withTimeout
26
- import kotlin.coroutines.resume
27
- import kotlin.coroutines.resumeWithException
28
-
29
- // Custom exception for scan errors with error codes
30
- class ScanException(val errorCode: String, val code: Int, message: String) : Exception(message)
31
-
32
- @ReactModule(name = Flic2Module.NAME)
33
- class Flic2Module(reactContext: ReactApplicationContext) :
34
- NativeFlic2Spec(reactContext) {
35
-
36
- private var flic2Service: Flic2Service? = null
37
- private var serviceBound = false
38
- private val moduleScope = CoroutineScope(Dispatchers.Main + Job())
39
- private var scanJob: Job? = null
40
- private val buttonListeners = mutableMapOf<String, Flic2ButtonEventListener>()
41
- private var initializePromise: Promise? = null
42
-
43
- companion object {
44
- const val NAME = "Flic2"
45
- private const val TAG = "Flic2Module"
46
- }
47
-
48
- private val serviceConnection = object : ServiceConnection {
49
- override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
50
- Log.d(TAG, "Service connected")
51
- val binder = service as Flic2Service.Flic2ServiceBinder
52
- flic2Service = binder.getService()
53
- serviceBound = true
54
-
55
- // Set up listeners for existing buttons
56
- flic2Service?.getManager()?.let { manager ->
57
- manager.buttons.forEach { button ->
58
- setupButtonListener(button)
59
- }
60
- }
61
-
62
- // Resolve the initialize promise if pending
63
- initializePromise?.let { promise ->
64
- promise.resolve(Arguments.createMap().apply {
65
- putBoolean("success", true)
66
- putString("message", "Manager initialized successfully")
67
- })
68
- initializePromise = null
69
- }
70
- }
71
-
72
- override fun onServiceDisconnected(name: ComponentName?) {
73
- Log.d(TAG, "Service disconnected")
74
- serviceBound = false
75
- flic2Service = null
76
-
77
- // Reject any pending initialize promise
78
- initializePromise?.let { promise ->
79
- promise.reject("SERVICE_DISCONNECTED", "Service disconnected unexpectedly")
80
- initializePromise = null
81
- }
82
- }
83
- }
84
-
85
- override fun getName(): String {
86
- return NAME
87
- }
88
-
89
- override fun invalidate() {
90
- super.invalidate()
91
- moduleScope.cancel()
92
- if (serviceBound) {
93
- reactApplicationContext.unbindService(serviceConnection)
94
- serviceBound = false
95
- }
96
- }
97
-
98
- // MARK: - Manager Methods
99
-
100
- override fun initialize(background: Boolean, promise: Promise) {
101
- try {
102
- // Store the promise to resolve when service is connected
103
- initializePromise = promise
104
-
105
- val intent = Intent(reactApplicationContext, Flic2Service::class.java)
106
-
107
- // Start service
108
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
109
- reactApplicationContext.startForegroundService(intent)
110
- } else {
111
- reactApplicationContext.startService(intent)
112
- }
113
-
114
- // Bind to service - promise will be resolved in onServiceConnected
115
- val bound = reactApplicationContext.bindService(
116
- intent,
117
- serviceConnection,
118
- Context.BIND_AUTO_CREATE
119
- )
120
-
121
- if (!bound) {
122
- initializePromise = null
123
- promise.reject("INIT_ERROR", "Failed to bind to service")
124
- }
125
- } catch (e: Exception) {
126
- Log.e(TAG, "Failed to initialize", e)
127
- initializePromise = null
128
- promise.reject("INIT_ERROR", "Failed to initialize: ${e.message}", e)
129
- }
130
- }
131
-
132
- override fun getButtons(promise: Promise) {
133
- try {
134
- val manager = flic2Service?.getManager()
135
- if (manager == null) {
136
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
137
- return
138
- }
139
-
140
- val buttons = manager.buttons
141
- val buttonArray = Flic2Converter.buttonsToArray(buttons)
142
- promise.resolve(buttonArray)
143
- } catch (e: Exception) {
144
- Log.e(TAG, "Failed to get buttons", e)
145
- promise.reject("GET_BUTTONS_ERROR", "Failed to get buttons: ${e.message}", e)
146
- }
147
- }
148
-
149
- override fun scanForButtons(promise: Promise) {
150
- val manager = flic2Service?.getManager()
151
- if (manager == null) {
152
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
153
- return
154
- }
155
-
156
- // Cancel any existing scan
157
- scanJob?.cancel()
158
-
159
- Log.d(TAG, "Starting scan")
160
-
161
- // Emit started event (matches iOS)
162
- emitOnScanStatusChange(Arguments.createMap().apply {
163
- putString("event", "started")
164
- putString("eventName", "started")
165
- })
166
-
167
- manager.startScan(object : Flic2ScanCallback {
168
- override fun onDiscoveredAlreadyPairedButton(button: Flic2Button) {
169
- Log.d(TAG, "Discovered already paired button")
170
- }
171
-
172
- override fun onDiscovered(bdAddr: String) {
173
- Log.d(TAG, "Discovered button: $bdAddr")
174
- }
175
-
176
- override fun onConnected() {
177
- Log.d(TAG, "Button connected during scan")
178
- }
179
-
180
- override fun onComplete(result: Int, subCode: Int, button: Flic2Button?) {
181
- Log.d(TAG, "Scan complete: result=$result, button=${button?.uuid}")
182
-
183
- val resultCode = mapScanResultToCode(result)
184
-
185
- if (result == Flic2ScanCallback.RESULT_SUCCESS && button != null) {
186
- // Auto-connect (trigger mode not available in Android v1.1.0+)
187
- button.connect()
188
-
189
- setupButtonListener(button)
190
-
191
- // Emit discovered event as button event (like iOS)
192
- emitOnButtonEvent(Arguments.createMap().apply {
193
- putString("uuid", button.uuid)
194
- putString("event", "discovered")
195
- putMap("button", Flic2Converter.buttonToMap(button))
196
- })
197
- } else {
198
- val errorCode = Flic2Converter.scanResultToString(result)
199
- Log.e(TAG, "Scan failed with error code: $errorCode")
200
- }
201
-
202
- // Emit scan completion with result code
203
- emitOnScanStatusChange(Arguments.createMap().apply {
204
- putString("event", "completion")
205
- putString("eventName", "completion")
206
- putInt("result", resultCode)
207
- })
208
- }
209
- })
210
-
211
- // Return immediately - scan results will come through events
212
- promise.resolve(Arguments.createMap().apply {
213
- putBoolean("success", true)
214
- putString("message", "Scan started")
215
- })
216
- }
217
-
218
- override fun stopScan(promise: Promise) {
219
- try {
220
- val manager = flic2Service?.getManager()
221
- if (manager == null) {
222
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
223
- return
224
- }
225
-
226
- scanJob?.cancel()
227
- manager.stopScan()
228
-
229
- promise.resolve(Arguments.createMap().apply {
230
- putBoolean("success", true)
231
- putString("message", "Scan stopped")
232
- })
233
- } catch (e: Exception) {
234
- Log.e(TAG, "Failed to stop scan", e)
235
- promise.reject("STOP_SCAN_ERROR", "Failed to stop scan: ${e.message}", e)
236
- }
237
- }
238
-
239
- override fun forgetButton(uuid: String, promise: Promise) {
240
- try {
241
- val manager = flic2Service?.getManager()
242
- if (manager == null) {
243
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
244
- return
245
- }
246
-
247
- val button = manager.buttons.find { it.uuid == uuid }
248
- if (button == null) {
249
- promise.reject("BUTTON_NOT_FOUND", "Button not found")
250
- return
251
- }
252
-
253
- // Disconnect before forgetting like iOS
254
- button.disconnectOrAbortPendingConnection()
255
-
256
- // Remove listener
257
- buttonListeners.remove(uuid)
258
-
259
- // Forget button
260
- manager.forgetButton(button)
261
-
262
- promise.resolve(Arguments.createMap().apply {
263
- putBoolean("success", true)
264
- putString("message", "Button forgotten")
265
- })
266
- } catch (e: Exception) {
267
- Log.e(TAG, "Failed to forget button", e)
268
- promise.reject("FORGET_ERROR", "Failed to forget button: ${e.message}", e)
269
- }
270
- }
271
-
272
- // MARK: - Button Methods
273
-
274
- override fun connectButton(uuid: String, promise: Promise) {
275
- try {
276
- val button = findButton(uuid)
277
- if (button == null) {
278
- promise.reject("BUTTON_NOT_FOUND", "Button not found")
279
- return
280
- }
281
-
282
- button.connect()
283
-
284
- promise.resolve(Arguments.createMap().apply {
285
- putBoolean("success", true)
286
- putString("message", "Connection initiated")
287
- })
288
- } catch (e: Exception) {
289
- Log.e(TAG, "Failed to connect button", e)
290
- promise.reject("CONNECT_ERROR", "Failed to connect: ${e.message}", e)
291
- }
292
- }
293
-
294
- override fun disconnectButton(uuid: String, promise: Promise) {
295
- try {
296
- val button = findButton(uuid)
297
- if (button == null) {
298
- promise.reject("BUTTON_NOT_FOUND", "Button not found")
299
- return
300
- }
301
-
302
- button.disconnectOrAbortPendingConnection()
303
-
304
- promise.resolve(Arguments.createMap().apply {
305
- putBoolean("success", true)
306
- putString("message", "Disconnection initiated")
307
- })
308
- } catch (e: Exception) {
309
- Log.e(TAG, "Failed to disconnect button", e)
310
- promise.reject("DISCONNECT_ERROR", "Failed to disconnect: ${e.message}", e)
311
- }
312
- }
313
-
314
- override fun setTriggerMode(uuid: String, mode: Double, promise: Promise) {
315
- promise.reject(
316
- "NOT_SUPPORTED_ON_ANDROID",
317
- "Trigger mode is only supported on iOS. Android Flic2 library v1.1.0+ does not support trigger modes."
318
- )
319
- }
320
-
321
- override fun setLatencyMode(uuid: String, mode: Double, promise: Promise) {
322
- promise.reject(
323
- "NOT_SUPPORTED_ON_ANDROID",
324
- "Latency mode is only supported on iOS. Android Flic2 library v1.1.0+ does not support latency modes."
325
- )
326
- }
327
-
328
- override fun setNickname(uuid: String, nickname: String, promise: Promise) {
329
- try {
330
- val button = findButton(uuid)
331
- if (button == null) {
332
- promise.reject("BUTTON_NOT_FOUND", "Button not found")
333
- return
334
- }
335
-
336
- // v1.1.0 uses setName() method instead of property
337
- button.setName(nickname)
338
-
339
- promise.resolve(Arguments.createMap().apply {
340
- putBoolean("success", true)
341
- putString("message", "Nickname set")
342
- })
343
- } catch (e: Exception) {
344
- Log.e(TAG, "Failed to set nickname", e)
345
- promise.reject("SET_NICKNAME_ERROR", "Failed to set nickname: ${e.message}", e)
346
- }
347
- }
348
-
349
- override fun connectAllKnownButtons(promise: Promise) {
350
- try {
351
- val manager = flic2Service?.getManager()
352
- if (manager == null) {
353
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
354
- return
355
- }
356
-
357
- val buttons = manager.buttons
358
-
359
- buttons.forEach { button ->
360
- Log.d(TAG, "Connecting button: ${button.getName()}")
361
- // Trigger mode not available in Android v1.1.0+
362
- button.connect()
363
- }
364
-
365
- promise.resolve(Arguments.createMap().apply {
366
- putBoolean("success", true)
367
- putString("message", "All buttons connection initiated")
368
- })
369
- } catch (e: Exception) {
370
- Log.e(TAG, "Failed to connect all buttons", e)
371
- promise.reject("CONNECT_ALL_ERROR", "Failed to connect all buttons: ${e.message}", e)
372
- }
373
- }
374
-
375
- override fun disconnectAllKnownButtons(promise: Promise) {
376
- try {
377
- val manager = flic2Service?.getManager()
378
- if (manager == null) {
379
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
380
- return
381
- }
382
-
383
- val buttons = manager.buttons
384
-
385
- buttons.forEach { button ->
386
- Log.d(TAG, "Disconnecting button: ${button.name}")
387
- button.disconnectOrAbortPendingConnection()
388
- }
389
-
390
- promise.resolve(Arguments.createMap().apply {
391
- putBoolean("success", true)
392
- putString("message", "All buttons disconnection initiated")
393
- })
394
- } catch (e: Exception) {
395
- Log.e(TAG, "Failed to disconnect all buttons", e)
396
- promise.reject("DISCONNECT_ALL_ERROR", "Failed to disconnect all buttons: ${e.message}", e)
397
- }
398
- }
399
-
400
- override fun forgetAllButtons(promise: Promise) {
401
- try {
402
- val manager = flic2Service?.getManager()
403
- if (manager == null) {
404
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
405
- return
406
- }
407
-
408
- // Create a copy of the list to avoid concurrent modification
409
- val buttons = manager.buttons.toList()
410
-
411
- buttons.forEach { button ->
412
- buttonListeners.remove(button.uuid)
413
- button.disconnectOrAbortPendingConnection()
414
- manager.forgetButton(button)
415
- }
416
-
417
- promise.resolve(Arguments.createMap().apply {
418
- putBoolean("success", true)
419
- putString("message", "All buttons forgotten")
420
- })
421
- } catch (e: Exception) {
422
- Log.e(TAG, "Failed to forget all buttons", e)
423
- promise.reject("FORGET_ALL_ERROR", "Failed to forget all buttons: ${e.message}", e)
424
- }
425
- }
426
-
427
- override fun isScanning(promise: Promise) {
428
- try {
429
- val manager = flic2Service?.getManager()
430
- if (manager == null) {
431
- promise.reject("NOT_INITIALIZED", "Manager not initialized")
432
- return
433
- }
434
-
435
- val scanning = (scanJob != null && scanJob?.isActive == true)
436
- promise.resolve(scanning)
437
- } catch (e: Exception) {
438
- Log.e(TAG, "Failed to check scanning status", e)
439
- promise.reject("IS_SCANNING_ERROR", "Failed to check scanning status: ${e.message}", e)
440
- }
441
- }
442
-
443
- // MARK: - Helper Methods
444
-
445
- private fun findButton(uuid: String): Flic2Button? {
446
- return flic2Service?.getManager()?.buttons?.find { it.uuid == uuid }
447
- }
448
-
449
- private fun setupButtonListener(button: Flic2Button) {
450
- // Remove existing listener if any
451
- buttonListeners.remove(button.uuid)
452
-
453
- // Create new listener
454
- val listener = Flic2ButtonEventListener { event ->
455
- emitOnButtonEvent(event)
456
- }
457
-
458
- // Add listener to button
459
- button.addListener(listener)
460
-
461
- // Store listener reference
462
- buttonListeners[button.uuid] = listener
463
- }
464
-
465
- private fun mapScanResultToCode(result: Int): Int {
466
- // Map Android library's 9 result codes (0-8) to TypeScript enum codes (0-21) matching iOS
467
- // Android library only provides these constants, so we map them to the closest equivalent
468
- return when (result) {
469
- Flic2ScanCallback.RESULT_SUCCESS -> 0 // SUCCESS
470
- Flic2ScanCallback.RESULT_FAILED_ALREADY_RUNNING -> 1 // ALREADY_RUNNING
471
- Flic2ScanCallback.RESULT_FAILED_BLUETOOTH_OFF -> 2 // BLUETOOTH_NOT_ACTIVATED
472
- Flic2ScanCallback.RESULT_FAILED_SCAN_ERROR -> 3 // UNKNOWN
473
- Flic2ScanCallback.RESULT_FAILED_NO_NEW_BUTTONS_FOUND -> 4 // NO_PUBLIC_BUTTON_DISCOVERED
474
- Flic2ScanCallback.RESULT_FAILED_BUTTON_ALREADY_CONNECTED_TO_OTHER_DEVICE -> 5 // ALREADY_CONNECTED_TO_ANOTHER_DEVICE
475
- Flic2ScanCallback.RESULT_FAILED_CONNECT_TIMED_OUT -> 6 // CONNECTION_TIMEOUT
476
- Flic2ScanCallback.RESULT_FAILED_VERIFY_TIMED_OUT -> 7 // INVALID_VERIFIER
477
- Flic2ScanCallback.RESULT_SYSTEM_PAIRING_DIALOG_NOT_ACCEPTED -> 9 // BLE_PAIRING_FAILED_USER_CANCELED
478
- else -> 3 // UNKNOWN (for any unexpected codes)
479
- }
480
- }
481
- }
@@ -1,33 +0,0 @@
1
- package com.flic2
2
-
3
- import com.facebook.react.BaseReactPackage
4
- import com.facebook.react.bridge.NativeModule
5
- import com.facebook.react.bridge.ReactApplicationContext
6
- import com.facebook.react.module.model.ReactModuleInfo
7
- import com.facebook.react.module.model.ReactModuleInfoProvider
8
- import java.util.HashMap
9
-
10
- class Flic2Package : BaseReactPackage() {
11
- override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
- return if (name == Flic2Module.NAME) {
13
- Flic2Module(reactContext)
14
- } else {
15
- null
16
- }
17
- }
18
-
19
- override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20
- return ReactModuleInfoProvider {
21
- val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22
- moduleInfos[Flic2Module.NAME] = ReactModuleInfo(
23
- Flic2Module.NAME,
24
- Flic2Module.NAME,
25
- false, // canOverrideExistingModule
26
- false, // needsEagerInit
27
- false, // isCxxModule
28
- true // isTurboModule
29
- )
30
- moduleInfos
31
- }
32
- }
33
- }