react-native-flic2 2.0.0-beta.2 → 2.0.0-beta.20

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/Flic2.podspec CHANGED
@@ -13,10 +13,20 @@ Pod::Spec.new do |s|
13
13
  s.platforms = { :ios => min_ios_version_supported }
14
14
  s.source = { :git => "https://github.com/X-Guard/react-native-flic2.git", :tag => "#{s.version}" }
15
15
 
16
- s.source_files = "ios/**/*.{h,m,mm,cpp}"
17
- s.private_header_files = "ios/**/*.h"
16
+ install_modules_dependencies(s)
18
17
 
19
- s.ios.vendored_frameworks = 'ios/flic2lib.framework'
18
+ # Keep the root spec codegen-only so consumers can opt into
19
+ # Device/Simulator implementations explicitly.
20
+ s.default_subspecs = []
20
21
 
21
- install_modules_dependencies(s)
22
+ s.subspec "Device" do |sp|
23
+ sp.source_files = "ios/Flic2.{h,mm}"
24
+ sp.private_header_files = "ios/Flic2.h"
25
+ sp.ios.vendored_frameworks = "ios/flic2lib.framework"
26
+ end
27
+
28
+ s.subspec "Simulator" do |sp|
29
+ sp.source_files = "ios/Flic2SimulatorStub.{h,mm}"
30
+ sp.private_header_files = "ios/Flic2SimulatorStub.h"
31
+ end
22
32
  end
@@ -0,0 +1,21 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "Flic2Device"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/X-Guard/react-native-flic2.git", :tag => "#{s.version}" }
15
+
16
+ install_modules_dependencies(s)
17
+
18
+ s.source_files = "ios/Flic2.{h,mm}"
19
+ s.private_header_files = "ios/Flic2.h"
20
+ s.ios.vendored_frameworks = "ios/flic2lib.framework"
21
+ end
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "Flic2Simulator"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/X-Guard/react-native-flic2.git", :tag => "#{s.version}" }
15
+
16
+ install_modules_dependencies(s)
17
+
18
+ s.source_files = "ios/Flic2SimulatorStub.{h,mm}"
19
+ s.private_header_files = "ios/Flic2SimulatorStub.h"
20
+ end
package/README.md CHANGED
@@ -37,6 +37,10 @@ npm install react-native-flic2
37
37
  cd ios && pod install && cd ..
38
38
  ```
39
39
 
40
+ **Need simulator builds?**
41
+ See `Troubleshooting` -> `Running on iOS Simulator` for a full configuration
42
+ example and launch commands.
43
+
40
44
  2. **Add Bluetooth permissions to `Info.plist`:**
41
45
  Add the following keys to your `ios/YourApp/Info.plist`:
42
46
  ```xml
@@ -66,6 +70,65 @@ The library automatically includes the necessary permissions in `AndroidManifest
66
70
 
67
71
  You'll need to request these permissions before scanning for buttons. Use a library like `react-native-permissions` or implement permission requests manually.
68
72
 
73
+ #### Customizing the Foreground Service Notification (Android)
74
+
75
+ 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`:
76
+
77
+ ```xml
78
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
79
+ <application>
80
+ <!-- Your existing application configuration -->
81
+
82
+ <!-- Customize Flic2 foreground service notification -->
83
+ <meta-data
84
+ android:name="nl.xguard.flic2.notification_title"
85
+ android:value="My Flic2 Service" />
86
+ <meta-data
87
+ android:name="nl.xguard.flic2.notification_text"
88
+ android:value="Flic2 buttons are active" />
89
+ <meta-data
90
+ android:name="nl.xguard.flic2.notification_icon"
91
+ android:resource="@drawable/ic_notification" />
92
+ <meta-data
93
+ android:name="nl.xguard.flic2.notification_channel_name"
94
+ android:value="Flic2 Notifications" />
95
+ <meta-data
96
+ android:name="nl.xguard.flic2.notification_channel_description"
97
+ android:value="Notifications for Flic2 button connections" />
98
+ <meta-data
99
+ android:name="nl.xguard.flic2.notification_id"
100
+ android:value="123321" />
101
+ <meta-data
102
+ android:name="nl.xguard.flic2.notification_channel_id"
103
+ android:value="my_custom_channel_id" />
104
+ </application>
105
+ </manifest>
106
+ ```
107
+
108
+ **Available Configuration Options:**
109
+
110
+ - `nl.xguard.flic2.notification_title` - Notification title (default: "Flic 2")
111
+ - `nl.xguard.flic2.notification_text` - Notification text (default: "Flic 2 service is running")
112
+ - `nl.xguard.flic2.notification_icon` - Notification icon resource ID (default: system info icon)
113
+ - Use `@drawable/your_icon_name` or `@mipmap/your_icon_name` format
114
+ - `nl.xguard.flic2.notification_channel_name` - Notification channel name (default: "Flic2Channel")
115
+ - `nl.xguard.flic2.notification_channel_description` - Notification channel description (default: "Flic2Channel")
116
+ - `nl.xguard.flic2.notification_id` - Notification ID integer (default: 123321)
117
+ - `nl.xguard.flic2.notification_channel_id` - Notification channel ID string (default: "Notification_Channel_Flic2Service")
118
+
119
+ **Example with Custom Icon:**
120
+
121
+ 1. Add your notification icon to `android/app/src/main/res/drawable/` (e.g., `ic_flic2_notification.png`)
122
+
123
+ 2. Add metadata to `AndroidManifest.xml`:
124
+ ```xml
125
+ <meta-data
126
+ android:name="nl.xguard.flic2.notification_icon"
127
+ android:resource="@drawable/ic_flic2_notification" />
128
+ ```
129
+
130
+ **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.
131
+
69
132
  ## Basic Usage
70
133
 
71
134
  ### 1. Initialize the Library (Global Setup)
@@ -390,7 +453,7 @@ export default Flic2Example;
390
453
 
391
454
  ### Initialization
392
455
 
393
- #### `initialize(): Promise<boolean>`
456
+ #### `initialize(): Promise<void>`
394
457
 
395
458
  Initialize the Flic2 manager. This must be called before using any other methods.
396
459
 
@@ -400,7 +463,7 @@ await Flic2.initialize();
400
463
 
401
464
  ### Scanning
402
465
 
403
- #### `startScan(): Promise<{ success: boolean; message: string }>`
466
+ #### `startScan(): Promise<void>`
404
467
 
405
468
  Start scanning for new Flic2 buttons. The scan will emit `scanStatusChange` events.
406
469
 
@@ -408,7 +471,7 @@ Start scanning for new Flic2 buttons. The scan will emit `scanStatusChange` even
408
471
  await Flic2.startScan();
409
472
  ```
410
473
 
411
- #### `stopScan(): Promise<{ success: boolean; message: string }>`
474
+ #### `stopScan(): Promise<void>`
412
475
 
413
476
  Stop an ongoing scan.
414
477
 
@@ -442,7 +505,7 @@ Get a specific button by UUID.
442
505
  const button = await Flic2.getButton('button-uuid');
443
506
  ```
444
507
 
445
- #### `connectAllKnownButtons(): Promise<{ success: boolean; message: string }>`
508
+ #### `connectAllKnownButtons(): Promise<void>`
446
509
 
447
510
  Connect to all previously known buttons.
448
511
 
@@ -450,7 +513,7 @@ Connect to all previously known buttons.
450
513
  await Flic2.connectAllKnownButtons();
451
514
  ```
452
515
 
453
- #### `disconnectAllKnownButtons(): Promise<{ success: boolean; message: string }>`
516
+ #### `disconnectAllKnownButtons(): Promise<void>`
454
517
 
455
518
  Disconnect all connected buttons.
456
519
 
@@ -458,7 +521,7 @@ Disconnect all connected buttons.
458
521
  await Flic2.disconnectAllKnownButtons();
459
522
  ```
460
523
 
461
- #### `forgetButton(uuid: string): Promise<{ success: boolean; message: string }>`
524
+ #### `forgetButton(uuid: string): Promise<void>`
462
525
 
463
526
  Forget (unpair) a specific button.
464
527
 
@@ -466,7 +529,7 @@ Forget (unpair) a specific button.
466
529
  await Flic2.forgetButton('button-uuid');
467
530
  ```
468
531
 
469
- #### `forgetAllButtons(): Promise<{ success: boolean; message: string }>`
532
+ #### `forgetAllButtons(): Promise<void>`
470
533
 
471
534
  Forget all buttons.
472
535
 
@@ -476,50 +539,54 @@ await Flic2.forgetAllButtons();
476
539
 
477
540
  ### Button Configuration
478
541
 
479
- #### `buttonConnect(uuid: string): Promise<{ success: boolean; message: string }>`
542
+ #### `buttonConnect(uuid: string): Promise<FlicButton>`
480
543
 
481
- Connect to a specific button.
544
+ Connect to a specific button. Returns the button object.
482
545
 
483
546
  ```tsx
484
- await Flic2.buttonConnect('button-uuid');
547
+ const button = await Flic2.buttonConnect('button-uuid');
485
548
  ```
486
549
 
487
- #### `buttonDisconnect(uuid: string): Promise<{ success: boolean; message: string }>`
550
+ #### `buttonDisconnect(uuid: string): Promise<FlicButton>`
488
551
 
489
- Disconnect a specific button.
552
+ Disconnect a specific button. Returns the button object.
490
553
 
491
554
  ```tsx
492
- await Flic2.buttonDisconnect('button-uuid');
555
+ const button = await Flic2.buttonDisconnect('button-uuid');
493
556
  ```
494
557
 
495
- #### `buttonSetNickname(uuid: string, nickname: string): Promise<{ success: boolean; message: string }>`
558
+ #### `buttonSetNickname(uuid: string, nickname: string): Promise<FlicButton>`
496
559
 
497
- Set a custom nickname for a button.
560
+ Set a custom nickname for a button. Returns the updated button object.
498
561
 
499
562
  ```tsx
500
- await Flic2.buttonSetNickname('button-uuid', 'My Button');
563
+ const button = await Flic2.buttonSetNickname('button-uuid', 'My Button');
501
564
  ```
502
565
 
503
- #### `buttonSetTriggerMode(uuid: string, mode: TriggerModeType): Promise<{ success: boolean; message: string }>`
566
+ #### `buttonSetTriggerMode(uuid: string, mode: TriggerModeType): Promise<FlicButton>`
504
567
 
505
- Set the trigger mode for a button. Modes:
568
+ Set the trigger mode for a button. Returns the updated button object. Modes:
506
569
  - `0`: Click and Hold
507
570
  - `1`: Click and Double Click
508
571
  - `2`: Click and Double Click and Hold
509
572
  - `3`: Click only
510
573
 
574
+ **Note:** This method is only supported on iOS. On Android, it will reject with an error.
575
+
511
576
  ```tsx
512
- await Flic2.buttonSetTriggerMode('button-uuid', 3); // Click only
577
+ const button = await Flic2.buttonSetTriggerMode('button-uuid', 3); // Click only
513
578
  ```
514
579
 
515
- #### `buttonSetLatencyMode(uuid: string, mode: LatencyModeType): Promise<{ success: boolean; message: string }>`
580
+ #### `buttonSetLatencyMode(uuid: string, mode: LatencyModeType): Promise<FlicButton>`
516
581
 
517
- Set the latency mode for a button. Modes:
582
+ Set the latency mode for a button. Returns the updated button object. Modes:
518
583
  - `0`: Normal latency
519
584
  - `1`: Low latency
520
585
 
586
+ **Note:** This method is only supported on iOS. On Android, it will reject with an error.
587
+
521
588
  ```tsx
522
- await Flic2.buttonSetLatencyMode('button-uuid', 1); // Low latency
589
+ const button = await Flic2.buttonSetLatencyMode('button-uuid', 1); // Low latency
523
590
  ```
524
591
 
525
592
  #### `getBatteryHealth(uuid: string): Promise<boolean>`
@@ -728,6 +795,52 @@ const scanForButtons = async () => {
728
795
 
729
796
  ## Troubleshooting
730
797
 
798
+ ### Running on iOS Simulator
799
+
800
+ `flic2lib` is device-only. For simulator builds, use the no-op simulator pod and a
801
+ dedicated simulator build configuration.
802
+
803
+ 1. Add a simulator configuration in Xcode (for example `DebugSimulator`) and a scheme that uses it.
804
+ 2. Map pods by configuration in your app `Podfile`:
805
+
806
+ ```ruby
807
+ # Example
808
+ project 'YourApp.xcodeproj', {
809
+ 'Debug' => :debug,
810
+ 'DebugSimulator' => :debug,
811
+ 'Release' => :release,
812
+ }
813
+
814
+ target 'YourApp' do
815
+ # ... your existing use_react_native! setup
816
+
817
+ pod 'Flic2Device',
818
+ :path => '../node_modules/react-native-flic2',
819
+ :configurations => ['Debug', 'Release']
820
+
821
+ pod 'Flic2Simulator',
822
+ :path => '../node_modules/react-native-flic2',
823
+ :configurations => ['DebugSimulator']
824
+ end
825
+ ```
826
+
827
+ 3. Install pods normally (no env flags):
828
+
829
+ ```sh
830
+ cd ios && bundle exec pod install && cd ..
831
+ ```
832
+
833
+ 4. Launch simulator build (example):
834
+
835
+ ```sh
836
+ npx react-native run-ios --scheme YourAppSimulator --mode DebugSimulator --simulator "iPhone 17"
837
+ ```
838
+
839
+ Expected behavior on simulator:
840
+ - App builds and launches.
841
+ - Flic calls are no-op fallback behavior.
842
+ - Real button communication works only on physical devices.
843
+
731
844
  ### Buttons not connecting
732
845
 
733
846
  - Ensure Bluetooth is enabled on the device
@@ -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
 
@@ -1,4 +1,4 @@
1
- package com.flic2
1
+ package nl.xguard.flic2
2
2
 
3
3
  import android.app.ActivityManager
4
4
  import android.content.Context
@@ -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.getReadyTimestamp() - 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.getReadyTimestamp() - 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
@@ -63,10 +62,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
63
62
 
64
63
  // Resolve the initialize promise if pending
65
64
  initializePromise?.let { promise ->
66
- promise.resolve(Arguments.createMap().apply {
67
- putBoolean("success", true)
68
- putString("message", "Manager initialized successfully")
69
- })
65
+ promise.resolve(null)
70
66
  initializePromise = null
71
67
  }
72
68
  }
@@ -90,6 +86,31 @@ class Flic2Module(reactContext: ReactApplicationContext) :
90
86
 
91
87
  override fun invalidate() {
92
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
+
93
114
  moduleScope.cancel()
94
115
  if (serviceBound) {
95
116
  reactApplicationContext.unbindService(serviceConnection)
@@ -107,8 +128,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
107
128
  val intent = Intent(reactApplicationContext, Flic2Service::class.java)
108
129
 
109
130
  // Check if service is already running
110
- val isRunning = ActivityUtil.isServiceRunning(reactApplicationContext, Flic2Service::class.java)
111
- if (!isRunning) {
131
+ if (!ActivityUtil.isServiceRunning(reactApplicationContext, Flic2Service::class.java)) {
112
132
  // Start service
113
133
  ActivityUtil.startForegroundService(reactApplicationContext, intent)
114
134
  }
@@ -182,8 +202,6 @@ class Flic2Module(reactContext: ReactApplicationContext) :
182
202
  override fun onComplete(result: Int, subCode: Int, button: Flic2Button?) {
183
203
  Log.d(TAG, "Scan complete: result=$result, button=${button?.uuid}")
184
204
 
185
- val resultCode = mapScanResultToCode(result)
186
-
187
205
  if (result == Flic2ScanCallback.RESULT_SUCCESS && button != null) {
188
206
  // Auto-connect (trigger mode not available in Android v1.1.0+)
189
207
  button.connect()
@@ -202,24 +220,20 @@ class Flic2Module(reactContext: ReactApplicationContext) :
202
220
  putMap("button", Flic2Converter.buttonToMap(button))
203
221
  })
204
222
  } else {
205
- val errorCode = Flic2Converter.scanResultToString(result)
206
- Log.e(TAG, "Scan failed with error code: $errorCode")
223
+ Log.e(TAG, "Scan failed with error code: ${Flic2Converter.scanResultToString(result)}")
207
224
  }
208
225
 
209
226
  // Emit scan completion with result code
210
227
  emitOnScanStatusChange(Arguments.createMap().apply {
211
228
  putString("event", "completion")
212
229
  putString("eventName", "completion")
213
- putInt("result", resultCode)
230
+ putInt("result", mapScanResultToCode(result))
214
231
  })
215
232
  }
216
233
  })
217
234
 
218
235
  // Return immediately - scan results will come through events
219
- promise.resolve(Arguments.createMap().apply {
220
- putBoolean("success", true)
221
- putString("message", "Scan started")
222
- })
236
+ promise.resolve(null)
223
237
  }
224
238
 
225
239
  override fun stopScan(promise: Promise) {
@@ -233,10 +247,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
233
247
  scanJob?.cancel()
234
248
  manager.stopScan()
235
249
 
236
- promise.resolve(Arguments.createMap().apply {
237
- putBoolean("success", true)
238
- putString("message", "Scan stopped")
239
- })
250
+ promise.resolve(null)
240
251
  } catch (e: Exception) {
241
252
  Log.e(TAG, "Failed to stop scan", e)
242
253
  promise.reject("STOP_SCAN_ERROR", "Failed to stop scan: ${e.message}", e)
@@ -260,7 +271,18 @@ class Flic2Module(reactContext: ReactApplicationContext) :
260
271
  // Disconnect before forgetting like iOS
261
272
  button.disconnectOrAbortPendingConnection()
262
273
 
263
- // 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
264
286
  buttonListeners.remove(uuid)
265
287
 
266
288
  // Forget button
@@ -269,10 +291,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
269
291
  // Update foreground service state after removing button
270
292
  updateForegroundServiceState(manager.buttons.size)
271
293
 
272
- promise.resolve(Arguments.createMap().apply {
273
- putBoolean("success", true)
274
- putString("message", "Button forgotten")
275
- })
294
+ promise.resolve(null)
276
295
  } catch (e: Exception) {
277
296
  Log.e(TAG, "Failed to forget button", e)
278
297
  promise.reject("FORGET_ERROR", "Failed to forget button: ${e.message}", e)
@@ -291,10 +310,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
291
310
 
292
311
  button.connect()
293
312
 
294
- promise.resolve(Arguments.createMap().apply {
295
- putBoolean("success", true)
296
- putString("message", "Connection initiated")
297
- })
313
+ promise.resolve(Flic2Converter.buttonToMap(button))
298
314
  } catch (e: Exception) {
299
315
  Log.e(TAG, "Failed to connect button", e)
300
316
  promise.reject("CONNECT_ERROR", "Failed to connect: ${e.message}", e)
@@ -311,10 +327,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
311
327
 
312
328
  button.disconnectOrAbortPendingConnection()
313
329
 
314
- promise.resolve(Arguments.createMap().apply {
315
- putBoolean("success", true)
316
- putString("message", "Disconnection initiated")
317
- })
330
+ promise.resolve(Flic2Converter.buttonToMap(button))
318
331
  } catch (e: Exception) {
319
332
  Log.e(TAG, "Failed to disconnect button", e)
320
333
  promise.reject("DISCONNECT_ERROR", "Failed to disconnect: ${e.message}", e)
@@ -346,10 +359,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
346
359
  // v1.1.0 uses setName() method instead of property
347
360
  button.setName(nickname)
348
361
 
349
- promise.resolve(Arguments.createMap().apply {
350
- putBoolean("success", true)
351
- putString("message", "Nickname set")
352
- })
362
+ promise.resolve(Flic2Converter.buttonToMap(button))
353
363
  } catch (e: Exception) {
354
364
  Log.e(TAG, "Failed to set nickname", e)
355
365
  promise.reject("SET_NICKNAME_ERROR", "Failed to set nickname: ${e.message}", e)
@@ -372,10 +382,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
372
382
  button.connect()
373
383
  }
374
384
 
375
- promise.resolve(Arguments.createMap().apply {
376
- putBoolean("success", true)
377
- putString("message", "All buttons connection initiated")
378
- })
385
+ promise.resolve(null)
379
386
  } catch (e: Exception) {
380
387
  Log.e(TAG, "Failed to connect all buttons", e)
381
388
  promise.reject("CONNECT_ALL_ERROR", "Failed to connect all buttons: ${e.message}", e)
@@ -397,10 +404,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
397
404
  button.disconnectOrAbortPendingConnection()
398
405
  }
399
406
 
400
- promise.resolve(Arguments.createMap().apply {
401
- putBoolean("success", true)
402
- putString("message", "All buttons disconnection initiated")
403
- })
407
+ promise.resolve(null)
404
408
  } catch (e: Exception) {
405
409
  Log.e(TAG, "Failed to disconnect all buttons", e)
406
410
  promise.reject("DISCONNECT_ALL_ERROR", "Failed to disconnect all buttons: ${e.message}", e)
@@ -419,18 +423,31 @@ class Flic2Module(reactContext: ReactApplicationContext) :
419
423
  val buttons = manager.buttons.toList()
420
424
 
421
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
422
438
  buttonListeners.remove(button.uuid)
439
+
440
+ // Disconnect before forgetting
423
441
  button.disconnectOrAbortPendingConnection()
442
+
443
+ // Forget button
424
444
  manager.forgetButton(button)
425
445
  }
426
446
 
427
447
  // Update foreground service state after removing all buttons
428
448
  updateForegroundServiceState(manager.buttons.size)
429
449
 
430
- promise.resolve(Arguments.createMap().apply {
431
- putBoolean("success", true)
432
- putString("message", "All buttons forgotten")
433
- })
450
+ promise.resolve(null)
434
451
  } catch (e: Exception) {
435
452
  Log.e(TAG, "Failed to forget all buttons", e)
436
453
  promise.reject("FORGET_ALL_ERROR", "Failed to forget all buttons: ${e.message}", e)
@@ -445,8 +462,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
445
462
  return
446
463
  }
447
464
 
448
- val scanning = (scanJob != null && scanJob?.isActive == true)
449
- promise.resolve(scanning)
465
+ promise.resolve(scanJob != null && scanJob?.isActive == true)
450
466
  } catch (e: Exception) {
451
467
  Log.e(TAG, "Failed to check scanning status", e)
452
468
  promise.reject("IS_SCANNING_ERROR", "Failed to check scanning status: ${e.message}", e)
@@ -502,3 +518,4 @@ class Flic2Module(reactContext: ReactApplicationContext) :
502
518
  }
503
519
  }
504
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
+