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

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/README.md CHANGED
@@ -66,6 +66,65 @@ The library automatically includes the necessary permissions in `AndroidManifest
66
66
 
67
67
  You'll need to request these permissions before scanning for buttons. Use a library like `react-native-permissions` or implement permission requests manually.
68
68
 
69
+ #### Customizing the Foreground Service Notification (Android)
70
+
71
+ The library runs a foreground service to keep Flic2 buttons connected in the background. You can customize the notification appearance by adding metadata to your app's `AndroidManifest.xml`:
72
+
73
+ ```xml
74
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
75
+ <application>
76
+ <!-- Your existing application configuration -->
77
+
78
+ <!-- Customize Flic2 foreground service notification -->
79
+ <meta-data
80
+ android:name="nl.xguard.flic2.notification_title"
81
+ android:value="My Flic2 Service" />
82
+ <meta-data
83
+ android:name="nl.xguard.flic2.notification_text"
84
+ android:value="Flic2 buttons are active" />
85
+ <meta-data
86
+ android:name="nl.xguard.flic2.notification_icon"
87
+ android:resource="@drawable/ic_notification" />
88
+ <meta-data
89
+ android:name="nl.xguard.flic2.notification_channel_name"
90
+ android:value="Flic2 Notifications" />
91
+ <meta-data
92
+ android:name="nl.xguard.flic2.notification_channel_description"
93
+ android:value="Notifications for Flic2 button connections" />
94
+ <meta-data
95
+ android:name="nl.xguard.flic2.notification_id"
96
+ android:value="123321" />
97
+ <meta-data
98
+ android:name="nl.xguard.flic2.notification_channel_id"
99
+ android:value="my_custom_channel_id" />
100
+ </application>
101
+ </manifest>
102
+ ```
103
+
104
+ **Available Configuration Options:**
105
+
106
+ - `nl.xguard.flic2.notification_title` - Notification title (default: "Flic 2")
107
+ - `nl.xguard.flic2.notification_text` - Notification text (default: "Flic 2 service is running")
108
+ - `nl.xguard.flic2.notification_icon` - Notification icon resource ID (default: system info icon)
109
+ - Use `@drawable/your_icon_name` or `@mipmap/your_icon_name` format
110
+ - `nl.xguard.flic2.notification_channel_name` - Notification channel name (default: "Flic2Channel")
111
+ - `nl.xguard.flic2.notification_channel_description` - Notification channel description (default: "Flic2Channel")
112
+ - `nl.xguard.flic2.notification_id` - Notification ID integer (default: 123321)
113
+ - `nl.xguard.flic2.notification_channel_id` - Notification channel ID string (default: "Notification_Channel_Flic2Service")
114
+
115
+ **Example with Custom Icon:**
116
+
117
+ 1. Add your notification icon to `android/app/src/main/res/drawable/` (e.g., `ic_flic2_notification.png`)
118
+
119
+ 2. Add metadata to `AndroidManifest.xml`:
120
+ ```xml
121
+ <meta-data
122
+ android:name="nl.xguard.flic2.notification_icon"
123
+ android:resource="@drawable/ic_flic2_notification" />
124
+ ```
125
+
126
+ **Note:** The notification icon must be a white/transparent icon suitable for Android notifications. If you don't specify a custom icon, the system default info icon will be used.
127
+
69
128
  ## Basic Usage
70
129
 
71
130
  ### 1. Initialize the Library (Global Setup)
@@ -390,7 +449,7 @@ export default Flic2Example;
390
449
 
391
450
  ### Initialization
392
451
 
393
- #### `initialize(): Promise<boolean>`
452
+ #### `initialize(): Promise<void>`
394
453
 
395
454
  Initialize the Flic2 manager. This must be called before using any other methods.
396
455
 
@@ -400,7 +459,7 @@ await Flic2.initialize();
400
459
 
401
460
  ### Scanning
402
461
 
403
- #### `startScan(): Promise<{ success: boolean; message: string }>`
462
+ #### `startScan(): Promise<void>`
404
463
 
405
464
  Start scanning for new Flic2 buttons. The scan will emit `scanStatusChange` events.
406
465
 
@@ -408,7 +467,7 @@ Start scanning for new Flic2 buttons. The scan will emit `scanStatusChange` even
408
467
  await Flic2.startScan();
409
468
  ```
410
469
 
411
- #### `stopScan(): Promise<{ success: boolean; message: string }>`
470
+ #### `stopScan(): Promise<void>`
412
471
 
413
472
  Stop an ongoing scan.
414
473
 
@@ -442,7 +501,7 @@ Get a specific button by UUID.
442
501
  const button = await Flic2.getButton('button-uuid');
443
502
  ```
444
503
 
445
- #### `connectAllKnownButtons(): Promise<{ success: boolean; message: string }>`
504
+ #### `connectAllKnownButtons(): Promise<void>`
446
505
 
447
506
  Connect to all previously known buttons.
448
507
 
@@ -450,7 +509,7 @@ Connect to all previously known buttons.
450
509
  await Flic2.connectAllKnownButtons();
451
510
  ```
452
511
 
453
- #### `disconnectAllKnownButtons(): Promise<{ success: boolean; message: string }>`
512
+ #### `disconnectAllKnownButtons(): Promise<void>`
454
513
 
455
514
  Disconnect all connected buttons.
456
515
 
@@ -458,7 +517,7 @@ Disconnect all connected buttons.
458
517
  await Flic2.disconnectAllKnownButtons();
459
518
  ```
460
519
 
461
- #### `forgetButton(uuid: string): Promise<{ success: boolean; message: string }>`
520
+ #### `forgetButton(uuid: string): Promise<void>`
462
521
 
463
522
  Forget (unpair) a specific button.
464
523
 
@@ -466,7 +525,7 @@ Forget (unpair) a specific button.
466
525
  await Flic2.forgetButton('button-uuid');
467
526
  ```
468
527
 
469
- #### `forgetAllButtons(): Promise<{ success: boolean; message: string }>`
528
+ #### `forgetAllButtons(): Promise<void>`
470
529
 
471
530
  Forget all buttons.
472
531
 
@@ -476,50 +535,54 @@ await Flic2.forgetAllButtons();
476
535
 
477
536
  ### Button Configuration
478
537
 
479
- #### `buttonConnect(uuid: string): Promise<{ success: boolean; message: string }>`
538
+ #### `buttonConnect(uuid: string): Promise<FlicButton>`
480
539
 
481
- Connect to a specific button.
540
+ Connect to a specific button. Returns the button object.
482
541
 
483
542
  ```tsx
484
- await Flic2.buttonConnect('button-uuid');
543
+ const button = await Flic2.buttonConnect('button-uuid');
485
544
  ```
486
545
 
487
- #### `buttonDisconnect(uuid: string): Promise<{ success: boolean; message: string }>`
546
+ #### `buttonDisconnect(uuid: string): Promise<FlicButton>`
488
547
 
489
- Disconnect a specific button.
548
+ Disconnect a specific button. Returns the button object.
490
549
 
491
550
  ```tsx
492
- await Flic2.buttonDisconnect('button-uuid');
551
+ const button = await Flic2.buttonDisconnect('button-uuid');
493
552
  ```
494
553
 
495
- #### `buttonSetNickname(uuid: string, nickname: string): Promise<{ success: boolean; message: string }>`
554
+ #### `buttonSetNickname(uuid: string, nickname: string): Promise<FlicButton>`
496
555
 
497
- Set a custom nickname for a button.
556
+ Set a custom nickname for a button. Returns the updated button object.
498
557
 
499
558
  ```tsx
500
- await Flic2.buttonSetNickname('button-uuid', 'My Button');
559
+ const button = await Flic2.buttonSetNickname('button-uuid', 'My Button');
501
560
  ```
502
561
 
503
- #### `buttonSetTriggerMode(uuid: string, mode: TriggerModeType): Promise<{ success: boolean; message: string }>`
562
+ #### `buttonSetTriggerMode(uuid: string, mode: TriggerModeType): Promise<FlicButton>`
504
563
 
505
- Set the trigger mode for a button. Modes:
564
+ Set the trigger mode for a button. Returns the updated button object. Modes:
506
565
  - `0`: Click and Hold
507
566
  - `1`: Click and Double Click
508
567
  - `2`: Click and Double Click and Hold
509
568
  - `3`: Click only
510
569
 
570
+ **Note:** This method is only supported on iOS. On Android, it will reject with an error.
571
+
511
572
  ```tsx
512
- await Flic2.buttonSetTriggerMode('button-uuid', 3); // Click only
573
+ const button = await Flic2.buttonSetTriggerMode('button-uuid', 3); // Click only
513
574
  ```
514
575
 
515
- #### `buttonSetLatencyMode(uuid: string, mode: LatencyModeType): Promise<{ success: boolean; message: string }>`
576
+ #### `buttonSetLatencyMode(uuid: string, mode: LatencyModeType): Promise<FlicButton>`
516
577
 
517
- Set the latency mode for a button. Modes:
578
+ Set the latency mode for a button. Returns the updated button object. Modes:
518
579
  - `0`: Normal latency
519
580
  - `1`: Low latency
520
581
 
582
+ **Note:** This method is only supported on iOS. On Android, it will reject with an error.
583
+
521
584
  ```tsx
522
- await Flic2.buttonSetLatencyMode('button-uuid', 1); // Low latency
585
+ const button = await Flic2.buttonSetLatencyMode('button-uuid', 1); // Low latency
523
586
  ```
524
587
 
525
588
  #### `getBatteryHealth(uuid: string): Promise<boolean>`
@@ -26,7 +26,7 @@ def getExtOrIntegerDefault(name) {
26
26
  }
27
27
 
28
28
  android {
29
- namespace "com.flic2"
29
+ namespace "nl.xguard.flic2"
30
30
 
31
31
  compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
32
32
 
@@ -15,6 +15,7 @@
15
15
  <!-- Foreground service permission -->
16
16
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
17
17
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
18
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
18
19
 
19
20
  <application>
20
21
  <service
@@ -22,6 +23,28 @@
22
23
  android:enabled="true"
23
24
  android:exported="false"
24
25
  android:foregroundServiceType="connectedDevice" />
26
+
27
+ <receiver
28
+ android:name=".Flic2Service$BootUpReceiver"
29
+ android:enabled="true"
30
+ android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
31
+ android:exported="false">
32
+ <intent-filter>
33
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
34
+ <category android:name="android.intent.category.DEFAULT" />
35
+ </intent-filter>
36
+ </receiver>
37
+
38
+ <receiver
39
+ android:name=".Flic2Service$UpdateReceiver"
40
+ android:enabled="true"
41
+ android:exported="false">
42
+ <intent-filter>
43
+ <action android:name="android.intent.action.PACKAGE_REPLACED" />
44
+ <data
45
+ android:scheme="package" />
46
+ </intent-filter>
47
+ </receiver>
25
48
  </application>
26
49
 
27
50
  </manifest>
@@ -0,0 +1,29 @@
1
+ package nl.xguard.flic2
2
+
3
+ import android.app.ActivityManager
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.os.Build
7
+
8
+ object ActivityUtil {
9
+ private const val TAG = "ActivityUtil"
10
+
11
+ fun isServiceRunning(context: Context, serviceClass: Class<*>): Boolean {
12
+ val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
13
+ for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
14
+ if (serviceClass.name == service.service.className) {
15
+ return true
16
+ }
17
+ }
18
+ return false
19
+ }
20
+
21
+ fun startForegroundService(context: Context, intent: Intent) {
22
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
23
+ context.startForegroundService(intent)
24
+ } else {
25
+ context.startService(intent)
26
+ }
27
+ }
28
+ }
29
+
@@ -1,4 +1,4 @@
1
- package com.flic2
1
+ package nl.xguard.flic2
2
2
 
3
3
  import com.facebook.react.bridge.Arguments
4
4
  import com.facebook.react.bridge.WritableMap
@@ -47,7 +47,15 @@ class Flic2ButtonEventListener(
47
47
  val event = if (isDown) "buttonDown" else "buttonUp"
48
48
  emitEvent(createButtonEvent(button, event).apply {
49
49
  putBoolean("queued", wasQueued)
50
- putDouble("age", System.currentTimeMillis() - timestamp.toDouble())
50
+ // Match old Android implementation and iOS:
51
+ // - Age is in seconds
52
+ // - Only meaningful for queued events; 0 for real-time events
53
+ val ageSeconds = if (wasQueued) {
54
+ (button.readyTimestamp - timestamp) / 1000.0
55
+ } else {
56
+ 0.0
57
+ }
58
+ putDouble("age", ageSeconds)
51
59
  })
52
60
  }
53
61
 
@@ -94,7 +102,15 @@ class Flic2ButtonEventListener(
94
102
  }
95
103
  emitEvent(createButtonEvent(button, event).apply {
96
104
  putBoolean("queued", wasQueued)
97
- putDouble("age", System.currentTimeMillis() - timestamp.toDouble())
105
+ // Match old Android implementation and iOS:
106
+ // - Age is in seconds
107
+ // - Only meaningful for queued events; 0 for real-time events
108
+ val ageSeconds = if (wasQueued) {
109
+ (button.readyTimestamp - timestamp) / 1000.0
110
+ } else {
111
+ 0.0
112
+ }
113
+ putDouble("age", ageSeconds)
98
114
  })
99
115
  }
100
116
 
@@ -1,4 +1,4 @@
1
- package com.flic2
1
+ package nl.xguard.flic2
2
2
 
3
3
  import com.facebook.react.bridge.Arguments
4
4
  import com.facebook.react.bridge.WritableArray
@@ -31,8 +31,7 @@ object Flic2Converter {
31
31
  putInt("firmwareRevision", button.getFirmwareVersion())
32
32
 
33
33
  // Check if ready by comparing connection state
34
- val isReady = connState == Flic2Button.CONNECTION_STATE_CONNECTED_READY
35
- putBoolean("isReady", isReady)
34
+ putBoolean("isReady", connState == Flic2Button.CONNECTION_STATE_CONNECTED_READY)
36
35
 
37
36
  // Get battery level from BatteryLevel object
38
37
  val batteryLevel = button.getLastKnownBatteryLevel()
@@ -1,4 +1,4 @@
1
- package com.flic2
1
+ package nl.xguard.flic2
2
2
 
3
3
  import android.content.ComponentName
4
4
  import android.content.Context
@@ -48,8 +48,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
48
48
  private val serviceConnection = object : ServiceConnection {
49
49
  override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
50
50
  Log.d(TAG, "Service connected")
51
- val binder = service as Flic2Service.Flic2ServiceBinder
52
- flic2Service = binder.getService()
51
+ flic2Service = (service as Flic2Service.Flic2ServiceBinder).getService()
53
52
  serviceBound = true
54
53
 
55
54
  // Set up listeners for existing buttons
@@ -57,14 +56,13 @@ class Flic2Module(reactContext: ReactApplicationContext) :
57
56
  manager.buttons.forEach { button ->
58
57
  setupButtonListener(button)
59
58
  }
59
+ // Update foreground service state based on button count
60
+ updateForegroundServiceState(manager.buttons.size)
60
61
  }
61
62
 
62
63
  // Resolve the initialize promise if pending
63
64
  initializePromise?.let { promise ->
64
- promise.resolve(Arguments.createMap().apply {
65
- putBoolean("success", true)
66
- putString("message", "Manager initialized successfully")
67
- })
65
+ promise.resolve(null)
68
66
  initializePromise = null
69
67
  }
70
68
  }
@@ -88,6 +86,31 @@ class Flic2Module(reactContext: ReactApplicationContext) :
88
86
 
89
87
  override fun invalidate() {
90
88
  super.invalidate()
89
+
90
+ // Remove all button listeners before cleanup to prevent callbacks after teardown
91
+ try {
92
+ val manager = flic2Service?.getManager()
93
+ if (manager != null) {
94
+ buttonListeners.forEach { (uuid, listener) ->
95
+ try {
96
+ // Find the button and remove the listener
97
+ val button = manager.buttons.find { it.uuid == uuid }
98
+ if (button != null) {
99
+ button.removeListener(listener)
100
+ Log.d(TAG, "Removed listener for button during invalidate: $uuid")
101
+ }
102
+ } catch (e: Exception) {
103
+ Log.w(TAG, "Failed to remove listener for button during invalidate: $uuid", e)
104
+ }
105
+ }
106
+ }
107
+ } catch (e: Exception) {
108
+ Log.w(TAG, "Error during listener cleanup in invalidate", e)
109
+ }
110
+
111
+ // Clear listeners map
112
+ buttonListeners.clear()
113
+
91
114
  moduleScope.cancel()
92
115
  if (serviceBound) {
93
116
  reactApplicationContext.unbindService(serviceConnection)
@@ -104,11 +127,10 @@ class Flic2Module(reactContext: ReactApplicationContext) :
104
127
 
105
128
  val intent = Intent(reactApplicationContext, Flic2Service::class.java)
106
129
 
107
- // Start service
108
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
109
- reactApplicationContext.startForegroundService(intent)
110
- } else {
111
- reactApplicationContext.startService(intent)
130
+ // Check if service is already running
131
+ if (!ActivityUtil.isServiceRunning(reactApplicationContext, Flic2Service::class.java)) {
132
+ // Start service
133
+ ActivityUtil.startForegroundService(reactApplicationContext, intent)
112
134
  }
113
135
 
114
136
  // Bind to service - promise will be resolved in onServiceConnected
@@ -180,14 +202,17 @@ class Flic2Module(reactContext: ReactApplicationContext) :
180
202
  override fun onComplete(result: Int, subCode: Int, button: Flic2Button?) {
181
203
  Log.d(TAG, "Scan complete: result=$result, button=${button?.uuid}")
182
204
 
183
- val resultCode = mapScanResultToCode(result)
184
-
185
205
  if (result == Flic2ScanCallback.RESULT_SUCCESS && button != null) {
186
206
  // Auto-connect (trigger mode not available in Android v1.1.0+)
187
207
  button.connect()
188
208
 
189
209
  setupButtonListener(button)
190
210
 
211
+ // Update foreground service state after adding button
212
+ flic2Service?.getManager()?.let { manager ->
213
+ updateForegroundServiceState(manager.buttons.size)
214
+ }
215
+
191
216
  // Emit discovered event as button event (like iOS)
192
217
  emitOnButtonEvent(Arguments.createMap().apply {
193
218
  putString("uuid", button.uuid)
@@ -195,24 +220,20 @@ class Flic2Module(reactContext: ReactApplicationContext) :
195
220
  putMap("button", Flic2Converter.buttonToMap(button))
196
221
  })
197
222
  } else {
198
- val errorCode = Flic2Converter.scanResultToString(result)
199
- Log.e(TAG, "Scan failed with error code: $errorCode")
223
+ Log.e(TAG, "Scan failed with error code: ${Flic2Converter.scanResultToString(result)}")
200
224
  }
201
225
 
202
226
  // Emit scan completion with result code
203
227
  emitOnScanStatusChange(Arguments.createMap().apply {
204
228
  putString("event", "completion")
205
229
  putString("eventName", "completion")
206
- putInt("result", resultCode)
230
+ putInt("result", mapScanResultToCode(result))
207
231
  })
208
232
  }
209
233
  })
210
234
 
211
235
  // Return immediately - scan results will come through events
212
- promise.resolve(Arguments.createMap().apply {
213
- putBoolean("success", true)
214
- putString("message", "Scan started")
215
- })
236
+ promise.resolve(null)
216
237
  }
217
238
 
218
239
  override fun stopScan(promise: Promise) {
@@ -226,10 +247,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
226
247
  scanJob?.cancel()
227
248
  manager.stopScan()
228
249
 
229
- promise.resolve(Arguments.createMap().apply {
230
- putBoolean("success", true)
231
- putString("message", "Scan stopped")
232
- })
250
+ promise.resolve(null)
233
251
  } catch (e: Exception) {
234
252
  Log.e(TAG, "Failed to stop scan", e)
235
253
  promise.reject("STOP_SCAN_ERROR", "Failed to stop scan: ${e.message}", e)
@@ -253,16 +271,27 @@ class Flic2Module(reactContext: ReactApplicationContext) :
253
271
  // Disconnect before forgetting like iOS
254
272
  button.disconnectOrAbortPendingConnection()
255
273
 
256
- // Remove listener
274
+ // Explicitly remove listener from button before forgetting (matches old implementation)
275
+ val listener = buttonListeners[uuid]
276
+ if (listener != null) {
277
+ try {
278
+ button.removeListener(listener)
279
+ Log.d(TAG, "Removed listener for button: $uuid")
280
+ } catch (e: Exception) {
281
+ Log.w(TAG, "Failed to remove listener for button: $uuid", e)
282
+ }
283
+ }
284
+
285
+ // Remove listener from map
257
286
  buttonListeners.remove(uuid)
258
287
 
259
288
  // Forget button
260
289
  manager.forgetButton(button)
261
290
 
262
- promise.resolve(Arguments.createMap().apply {
263
- putBoolean("success", true)
264
- putString("message", "Button forgotten")
265
- })
291
+ // Update foreground service state after removing button
292
+ updateForegroundServiceState(manager.buttons.size)
293
+
294
+ promise.resolve(null)
266
295
  } catch (e: Exception) {
267
296
  Log.e(TAG, "Failed to forget button", e)
268
297
  promise.reject("FORGET_ERROR", "Failed to forget button: ${e.message}", e)
@@ -281,10 +310,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
281
310
 
282
311
  button.connect()
283
312
 
284
- promise.resolve(Arguments.createMap().apply {
285
- putBoolean("success", true)
286
- putString("message", "Connection initiated")
287
- })
313
+ promise.resolve(Flic2Converter.buttonToMap(button))
288
314
  } catch (e: Exception) {
289
315
  Log.e(TAG, "Failed to connect button", e)
290
316
  promise.reject("CONNECT_ERROR", "Failed to connect: ${e.message}", e)
@@ -301,10 +327,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
301
327
 
302
328
  button.disconnectOrAbortPendingConnection()
303
329
 
304
- promise.resolve(Arguments.createMap().apply {
305
- putBoolean("success", true)
306
- putString("message", "Disconnection initiated")
307
- })
330
+ promise.resolve(Flic2Converter.buttonToMap(button))
308
331
  } catch (e: Exception) {
309
332
  Log.e(TAG, "Failed to disconnect button", e)
310
333
  promise.reject("DISCONNECT_ERROR", "Failed to disconnect: ${e.message}", e)
@@ -336,10 +359,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
336
359
  // v1.1.0 uses setName() method instead of property
337
360
  button.setName(nickname)
338
361
 
339
- promise.resolve(Arguments.createMap().apply {
340
- putBoolean("success", true)
341
- putString("message", "Nickname set")
342
- })
362
+ promise.resolve(Flic2Converter.buttonToMap(button))
343
363
  } catch (e: Exception) {
344
364
  Log.e(TAG, "Failed to set nickname", e)
345
365
  promise.reject("SET_NICKNAME_ERROR", "Failed to set nickname: ${e.message}", e)
@@ -362,10 +382,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
362
382
  button.connect()
363
383
  }
364
384
 
365
- promise.resolve(Arguments.createMap().apply {
366
- putBoolean("success", true)
367
- putString("message", "All buttons connection initiated")
368
- })
385
+ promise.resolve(null)
369
386
  } catch (e: Exception) {
370
387
  Log.e(TAG, "Failed to connect all buttons", e)
371
388
  promise.reject("CONNECT_ALL_ERROR", "Failed to connect all buttons: ${e.message}", e)
@@ -387,10 +404,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
387
404
  button.disconnectOrAbortPendingConnection()
388
405
  }
389
406
 
390
- promise.resolve(Arguments.createMap().apply {
391
- putBoolean("success", true)
392
- putString("message", "All buttons disconnection initiated")
393
- })
407
+ promise.resolve(null)
394
408
  } catch (e: Exception) {
395
409
  Log.e(TAG, "Failed to disconnect all buttons", e)
396
410
  promise.reject("DISCONNECT_ALL_ERROR", "Failed to disconnect all buttons: ${e.message}", e)
@@ -409,15 +423,31 @@ class Flic2Module(reactContext: ReactApplicationContext) :
409
423
  val buttons = manager.buttons.toList()
410
424
 
411
425
  buttons.forEach { button ->
426
+ // Explicitly remove listener from button before forgetting (matches old implementation)
427
+ val listener = buttonListeners[button.uuid]
428
+ if (listener != null) {
429
+ try {
430
+ button.removeListener(listener)
431
+ Log.d(TAG, "Removed listener for button: ${button.uuid}")
432
+ } catch (e: Exception) {
433
+ Log.w(TAG, "Failed to remove listener for button: ${button.uuid}", e)
434
+ }
435
+ }
436
+
437
+ // Remove listener from map
412
438
  buttonListeners.remove(button.uuid)
439
+
440
+ // Disconnect before forgetting
413
441
  button.disconnectOrAbortPendingConnection()
442
+
443
+ // Forget button
414
444
  manager.forgetButton(button)
415
445
  }
416
446
 
417
- promise.resolve(Arguments.createMap().apply {
418
- putBoolean("success", true)
419
- putString("message", "All buttons forgotten")
420
- })
447
+ // Update foreground service state after removing all buttons
448
+ updateForegroundServiceState(manager.buttons.size)
449
+
450
+ promise.resolve(null)
421
451
  } catch (e: Exception) {
422
452
  Log.e(TAG, "Failed to forget all buttons", e)
423
453
  promise.reject("FORGET_ALL_ERROR", "Failed to forget all buttons: ${e.message}", e)
@@ -432,8 +462,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
432
462
  return
433
463
  }
434
464
 
435
- val scanning = (scanJob != null && scanJob?.isActive == true)
436
- promise.resolve(scanning)
465
+ promise.resolve(scanJob != null && scanJob?.isActive == true)
437
466
  } catch (e: Exception) {
438
467
  Log.e(TAG, "Failed to check scanning status", e)
439
468
  promise.reject("IS_SCANNING_ERROR", "Failed to check scanning status: ${e.message}", e)
@@ -462,6 +491,16 @@ class Flic2Module(reactContext: ReactApplicationContext) :
462
491
  buttonListeners[button.uuid] = listener
463
492
  }
464
493
 
494
+ private fun updateForegroundServiceState(buttonCount: Int) {
495
+ if (buttonCount > 0) {
496
+ // Start foreground service when buttons exist
497
+ flic2Service?.startForegroundService()
498
+ } else {
499
+ // Stop foreground service when no buttons
500
+ flic2Service?.stopForegroundService()
501
+ }
502
+ }
503
+
465
504
  private fun mapScanResultToCode(result: Int): Int {
466
505
  // Map Android library's 9 result codes (0-8) to TypeScript enum codes (0-21) matching iOS
467
506
  // Android library only provides these constants, so we map them to the closest equivalent
@@ -479,3 +518,4 @@ class Flic2Module(reactContext: ReactApplicationContext) :
479
518
  }
480
519
  }
481
520
  }
521
+
@@ -1,4 +1,4 @@
1
- package com.flic2
1
+ package nl.xguard.flic2
2
2
 
3
3
  import com.facebook.react.BaseReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
@@ -31,3 +31,4 @@ class Flic2Package : BaseReactPackage() {
31
31
  }
32
32
  }
33
33
  }
34
+