react-native-flic2 2.0.0-alpha.39 → 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 +796 -13
- package/android/build.gradle +1 -1
- package/android/src/main/AndroidManifest.xml +23 -0
- package/android/src/main/java/nl/xguard/flic2/ActivityUtil.kt +29 -0
- package/android/src/main/java/{com → nl/xguard}/flic2/Flic2ButtonListener.kt +25 -13
- package/android/src/main/java/{com → nl/xguard}/flic2/Flic2Converter.kt +2 -3
- package/android/src/main/java/{com → nl/xguard}/flic2/Flic2Module.kt +126 -85
- package/android/src/main/java/{com → nl/xguard}/flic2/Flic2Package.kt +2 -1
- package/android/src/main/java/nl/xguard/flic2/Flic2Service.kt +274 -0
- package/ios/Flic2.h +0 -1
- package/ios/Flic2.mm +223 -195
- package/lib/module/NativeFlic2.js +26 -0
- package/lib/module/NativeFlic2.js.map +1 -1
- package/lib/module/index.js +248 -76
- package/lib/module/index.js.map +1 -1
- package/lib/module/lib/typedEventEmitter.js +39 -0
- package/lib/module/lib/typedEventEmitter.js.map +1 -0
- package/lib/typescript/src/NativeFlic2.d.ts +44 -60
- package/lib/typescript/src/NativeFlic2.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +163 -80
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/lib/typedEventEmitter.d.ts +15 -0
- package/lib/typescript/src/lib/typedEventEmitter.d.ts.map +1 -0
- package/package.json +25 -25
- package/src/NativeFlic2.ts +62 -47
- package/src/index.ts +360 -0
- package/src/lib/typedEventEmitter.ts +63 -0
- package/android/src/main/java/com/flic2/Flic2Service.kt +0 -112
- package/src/index.tsx +0 -159
package/android/build.gradle
CHANGED
|
@@ -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
|
|
1
|
+
package nl.xguard.flic2
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
4
|
import com.facebook.react.bridge.WritableMap
|
|
@@ -47,10 +47,20 @@ class Flic2ButtonEventListener(
|
|
|
47
47
|
val event = if (isDown) "buttonDown" else "buttonUp"
|
|
48
48
|
emitEvent(createButtonEvent(button, event).apply {
|
|
49
49
|
putBoolean("queued", wasQueued)
|
|
50
|
-
|
|
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
|
|
|
62
|
+
// Android library calls ALL applicable callback methods, causing duplicate events.
|
|
63
|
+
// Only onButtonSingleOrDoubleClickOrHold should emit events to match iOS behavior.
|
|
54
64
|
override fun onButtonClickOrHold(
|
|
55
65
|
button: Flic2Button,
|
|
56
66
|
wasQueued: Boolean,
|
|
@@ -59,13 +69,11 @@ class Flic2ButtonEventListener(
|
|
|
59
69
|
isClick: Boolean,
|
|
60
70
|
isHold: Boolean
|
|
61
71
|
) {
|
|
62
|
-
|
|
63
|
-
emitEvent(createButtonEvent(button, event).apply {
|
|
64
|
-
putBoolean("queued", wasQueued)
|
|
65
|
-
putDouble("age", System.currentTimeMillis() - timestamp.toDouble())
|
|
66
|
-
})
|
|
72
|
+
// Intentionally empty - events are handled by onButtonSingleOrDoubleClickOrHold
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
// Android library calls ALL applicable callback methods, causing duplicate events.
|
|
76
|
+
// Only onButtonSingleOrDoubleClickOrHold should emit events to match iOS behavior.
|
|
69
77
|
override fun onButtonSingleOrDoubleClick(
|
|
70
78
|
button: Flic2Button,
|
|
71
79
|
wasQueued: Boolean,
|
|
@@ -74,11 +82,7 @@ class Flic2ButtonEventListener(
|
|
|
74
82
|
isSingleClick: Boolean,
|
|
75
83
|
isDoubleClick: Boolean
|
|
76
84
|
) {
|
|
77
|
-
|
|
78
|
-
emitEvent(createButtonEvent(button, event).apply {
|
|
79
|
-
putBoolean("queued", wasQueued)
|
|
80
|
-
putDouble("age", System.currentTimeMillis() - timestamp.toDouble())
|
|
81
|
-
})
|
|
85
|
+
// Intentionally empty - events are handled by onButtonSingleOrDoubleClickOrHold
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
override fun onButtonSingleOrDoubleClickOrHold(
|
|
@@ -98,7 +102,15 @@ class Flic2ButtonEventListener(
|
|
|
98
102
|
}
|
|
99
103
|
emitEvent(createButtonEvent(button, event).apply {
|
|
100
104
|
putBoolean("queued", wasQueued)
|
|
101
|
-
|
|
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)
|
|
102
114
|
})
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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)
|
|
@@ -95,20 +118,6 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
95
118
|
}
|
|
96
119
|
}
|
|
97
120
|
|
|
98
|
-
// Example method - keep for reference
|
|
99
|
-
override fun multiply(a: Double, b: Double): Double {
|
|
100
|
-
val result = a * b
|
|
101
|
-
|
|
102
|
-
val eventData = Arguments.createMap().apply {
|
|
103
|
-
putDouble("a", a)
|
|
104
|
-
putDouble("b", b)
|
|
105
|
-
putDouble("result", result)
|
|
106
|
-
}
|
|
107
|
-
emitOnMultiply(eventData)
|
|
108
|
-
|
|
109
|
-
return result
|
|
110
|
-
}
|
|
111
|
-
|
|
112
121
|
// MARK: - Manager Methods
|
|
113
122
|
|
|
114
123
|
override fun initialize(background: Boolean, promise: Promise) {
|
|
@@ -118,11 +127,10 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
118
127
|
|
|
119
128
|
val intent = Intent(reactApplicationContext, Flic2Service::class.java)
|
|
120
129
|
|
|
121
|
-
//
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
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)
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
// Bind to service - promise will be resolved in onServiceConnected
|
|
@@ -172,63 +180,60 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
172
180
|
|
|
173
181
|
Log.d(TAG, "Starting scan")
|
|
174
182
|
|
|
183
|
+
// Emit started event (matches iOS)
|
|
184
|
+
emitOnScanStatusChange(Arguments.createMap().apply {
|
|
185
|
+
putString("event", "started")
|
|
186
|
+
putString("eventName", "started")
|
|
187
|
+
})
|
|
188
|
+
|
|
175
189
|
manager.startScan(object : Flic2ScanCallback {
|
|
176
190
|
override fun onDiscoveredAlreadyPairedButton(button: Flic2Button) {
|
|
177
191
|
Log.d(TAG, "Discovered already paired button")
|
|
178
|
-
emitOnScanStatusChange(Arguments.createMap().apply {
|
|
179
|
-
putInt("event", 0)
|
|
180
|
-
putString("eventName", "discovered")
|
|
181
|
-
})
|
|
182
192
|
}
|
|
183
193
|
|
|
184
194
|
override fun onDiscovered(bdAddr: String) {
|
|
185
195
|
Log.d(TAG, "Discovered button: $bdAddr")
|
|
186
|
-
emitOnScanStatusChange(Arguments.createMap().apply {
|
|
187
|
-
putInt("event", 0)
|
|
188
|
-
putString("eventName", "discovered")
|
|
189
|
-
})
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
override fun onConnected() {
|
|
193
199
|
Log.d(TAG, "Button connected during scan")
|
|
194
|
-
emitOnScanStatusChange(Arguments.createMap().apply {
|
|
195
|
-
putInt("event", 1)
|
|
196
|
-
putString("eventName", "connected")
|
|
197
|
-
})
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
override fun onComplete(result: Int, subCode: Int, button: Flic2Button?) {
|
|
201
203
|
Log.d(TAG, "Scan complete: result=$result, button=${button?.uuid}")
|
|
202
204
|
|
|
203
205
|
if (result == Flic2ScanCallback.RESULT_SUCCESS && button != null) {
|
|
204
|
-
emitOnScanStatusChange(Arguments.createMap().apply {
|
|
205
|
-
putInt("event", 2)
|
|
206
|
-
putString("eventName", "verified")
|
|
207
|
-
})
|
|
208
|
-
|
|
209
206
|
// Auto-connect (trigger mode not available in Android v1.1.0+)
|
|
210
207
|
button.connect()
|
|
211
208
|
|
|
212
209
|
setupButtonListener(button)
|
|
213
210
|
|
|
214
|
-
//
|
|
211
|
+
// Update foreground service state after adding button
|
|
212
|
+
flic2Service?.getManager()?.let { manager ->
|
|
213
|
+
updateForegroundServiceState(manager.buttons.size)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Emit discovered event as button event (like iOS)
|
|
215
217
|
emitOnButtonEvent(Arguments.createMap().apply {
|
|
216
218
|
putString("uuid", button.uuid)
|
|
217
219
|
putString("event", "discovered")
|
|
218
220
|
putMap("button", Flic2Converter.buttonToMap(button))
|
|
219
221
|
})
|
|
220
222
|
} else {
|
|
221
|
-
|
|
222
|
-
Log.e(TAG, "Scan failed with error code: $errorCode")
|
|
223
|
+
Log.e(TAG, "Scan failed with error code: ${Flic2Converter.scanResultToString(result)}")
|
|
223
224
|
}
|
|
225
|
+
|
|
226
|
+
// Emit scan completion with result code
|
|
227
|
+
emitOnScanStatusChange(Arguments.createMap().apply {
|
|
228
|
+
putString("event", "completion")
|
|
229
|
+
putString("eventName", "completion")
|
|
230
|
+
putInt("result", mapScanResultToCode(result))
|
|
231
|
+
})
|
|
224
232
|
}
|
|
225
233
|
})
|
|
226
234
|
|
|
227
235
|
// Return immediately - scan results will come through events
|
|
228
|
-
promise.resolve(
|
|
229
|
-
putBoolean("success", true)
|
|
230
|
-
putString("message", "Scan started")
|
|
231
|
-
})
|
|
236
|
+
promise.resolve(null)
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
override fun stopScan(promise: Promise) {
|
|
@@ -242,10 +247,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
242
247
|
scanJob?.cancel()
|
|
243
248
|
manager.stopScan()
|
|
244
249
|
|
|
245
|
-
promise.resolve(
|
|
246
|
-
putBoolean("success", true)
|
|
247
|
-
putString("message", "Scan stopped")
|
|
248
|
-
})
|
|
250
|
+
promise.resolve(null)
|
|
249
251
|
} catch (e: Exception) {
|
|
250
252
|
Log.e(TAG, "Failed to stop scan", e)
|
|
251
253
|
promise.reject("STOP_SCAN_ERROR", "Failed to stop scan: ${e.message}", e)
|
|
@@ -269,16 +271,27 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
269
271
|
// Disconnect before forgetting like iOS
|
|
270
272
|
button.disconnectOrAbortPendingConnection()
|
|
271
273
|
|
|
272
|
-
//
|
|
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
|
|
273
286
|
buttonListeners.remove(uuid)
|
|
274
287
|
|
|
275
288
|
// Forget button
|
|
276
289
|
manager.forgetButton(button)
|
|
277
290
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
291
|
+
// Update foreground service state after removing button
|
|
292
|
+
updateForegroundServiceState(manager.buttons.size)
|
|
293
|
+
|
|
294
|
+
promise.resolve(null)
|
|
282
295
|
} catch (e: Exception) {
|
|
283
296
|
Log.e(TAG, "Failed to forget button", e)
|
|
284
297
|
promise.reject("FORGET_ERROR", "Failed to forget button: ${e.message}", e)
|
|
@@ -297,10 +310,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
297
310
|
|
|
298
311
|
button.connect()
|
|
299
312
|
|
|
300
|
-
promise.resolve(
|
|
301
|
-
putBoolean("success", true)
|
|
302
|
-
putString("message", "Connection initiated")
|
|
303
|
-
})
|
|
313
|
+
promise.resolve(Flic2Converter.buttonToMap(button))
|
|
304
314
|
} catch (e: Exception) {
|
|
305
315
|
Log.e(TAG, "Failed to connect button", e)
|
|
306
316
|
promise.reject("CONNECT_ERROR", "Failed to connect: ${e.message}", e)
|
|
@@ -317,10 +327,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
317
327
|
|
|
318
328
|
button.disconnectOrAbortPendingConnection()
|
|
319
329
|
|
|
320
|
-
promise.resolve(
|
|
321
|
-
putBoolean("success", true)
|
|
322
|
-
putString("message", "Disconnection initiated")
|
|
323
|
-
})
|
|
330
|
+
promise.resolve(Flic2Converter.buttonToMap(button))
|
|
324
331
|
} catch (e: Exception) {
|
|
325
332
|
Log.e(TAG, "Failed to disconnect button", e)
|
|
326
333
|
promise.reject("DISCONNECT_ERROR", "Failed to disconnect: ${e.message}", e)
|
|
@@ -352,10 +359,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
352
359
|
// v1.1.0 uses setName() method instead of property
|
|
353
360
|
button.setName(nickname)
|
|
354
361
|
|
|
355
|
-
promise.resolve(
|
|
356
|
-
putBoolean("success", true)
|
|
357
|
-
putString("message", "Nickname set")
|
|
358
|
-
})
|
|
362
|
+
promise.resolve(Flic2Converter.buttonToMap(button))
|
|
359
363
|
} catch (e: Exception) {
|
|
360
364
|
Log.e(TAG, "Failed to set nickname", e)
|
|
361
365
|
promise.reject("SET_NICKNAME_ERROR", "Failed to set nickname: ${e.message}", e)
|
|
@@ -378,10 +382,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
378
382
|
button.connect()
|
|
379
383
|
}
|
|
380
384
|
|
|
381
|
-
promise.resolve(
|
|
382
|
-
putBoolean("success", true)
|
|
383
|
-
putString("message", "All buttons connection initiated")
|
|
384
|
-
})
|
|
385
|
+
promise.resolve(null)
|
|
385
386
|
} catch (e: Exception) {
|
|
386
387
|
Log.e(TAG, "Failed to connect all buttons", e)
|
|
387
388
|
promise.reject("CONNECT_ALL_ERROR", "Failed to connect all buttons: ${e.message}", e)
|
|
@@ -403,10 +404,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
403
404
|
button.disconnectOrAbortPendingConnection()
|
|
404
405
|
}
|
|
405
406
|
|
|
406
|
-
promise.resolve(
|
|
407
|
-
putBoolean("success", true)
|
|
408
|
-
putString("message", "All buttons disconnection initiated")
|
|
409
|
-
})
|
|
407
|
+
promise.resolve(null)
|
|
410
408
|
} catch (e: Exception) {
|
|
411
409
|
Log.e(TAG, "Failed to disconnect all buttons", e)
|
|
412
410
|
promise.reject("DISCONNECT_ALL_ERROR", "Failed to disconnect all buttons: ${e.message}", e)
|
|
@@ -425,15 +423,31 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
425
423
|
val buttons = manager.buttons.toList()
|
|
426
424
|
|
|
427
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
|
|
428
438
|
buttonListeners.remove(button.uuid)
|
|
439
|
+
|
|
440
|
+
// Disconnect before forgetting
|
|
429
441
|
button.disconnectOrAbortPendingConnection()
|
|
442
|
+
|
|
443
|
+
// Forget button
|
|
430
444
|
manager.forgetButton(button)
|
|
431
445
|
}
|
|
432
446
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
447
|
+
// Update foreground service state after removing all buttons
|
|
448
|
+
updateForegroundServiceState(manager.buttons.size)
|
|
449
|
+
|
|
450
|
+
promise.resolve(null)
|
|
437
451
|
} catch (e: Exception) {
|
|
438
452
|
Log.e(TAG, "Failed to forget all buttons", e)
|
|
439
453
|
promise.reject("FORGET_ALL_ERROR", "Failed to forget all buttons: ${e.message}", e)
|
|
@@ -448,8 +462,7 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
448
462
|
return
|
|
449
463
|
}
|
|
450
464
|
|
|
451
|
-
|
|
452
|
-
promise.resolve(scanning)
|
|
465
|
+
promise.resolve(scanJob != null && scanJob?.isActive == true)
|
|
453
466
|
} catch (e: Exception) {
|
|
454
467
|
Log.e(TAG, "Failed to check scanning status", e)
|
|
455
468
|
promise.reject("IS_SCANNING_ERROR", "Failed to check scanning status: ${e.message}", e)
|
|
@@ -477,4 +490,32 @@ class Flic2Module(reactContext: ReactApplicationContext) :
|
|
|
477
490
|
// Store listener reference
|
|
478
491
|
buttonListeners[button.uuid] = listener
|
|
479
492
|
}
|
|
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
|
+
|
|
504
|
+
private fun mapScanResultToCode(result: Int): Int {
|
|
505
|
+
// Map Android library's 9 result codes (0-8) to TypeScript enum codes (0-21) matching iOS
|
|
506
|
+
// Android library only provides these constants, so we map them to the closest equivalent
|
|
507
|
+
return when (result) {
|
|
508
|
+
Flic2ScanCallback.RESULT_SUCCESS -> 0 // SUCCESS
|
|
509
|
+
Flic2ScanCallback.RESULT_FAILED_ALREADY_RUNNING -> 1 // ALREADY_RUNNING
|
|
510
|
+
Flic2ScanCallback.RESULT_FAILED_BLUETOOTH_OFF -> 2 // BLUETOOTH_NOT_ACTIVATED
|
|
511
|
+
Flic2ScanCallback.RESULT_FAILED_SCAN_ERROR -> 3 // UNKNOWN
|
|
512
|
+
Flic2ScanCallback.RESULT_FAILED_NO_NEW_BUTTONS_FOUND -> 4 // NO_PUBLIC_BUTTON_DISCOVERED
|
|
513
|
+
Flic2ScanCallback.RESULT_FAILED_BUTTON_ALREADY_CONNECTED_TO_OTHER_DEVICE -> 5 // ALREADY_CONNECTED_TO_ANOTHER_DEVICE
|
|
514
|
+
Flic2ScanCallback.RESULT_FAILED_CONNECT_TIMED_OUT -> 6 // CONNECTION_TIMEOUT
|
|
515
|
+
Flic2ScanCallback.RESULT_FAILED_VERIFY_TIMED_OUT -> 7 // INVALID_VERIFIER
|
|
516
|
+
Flic2ScanCallback.RESULT_SYSTEM_PAIRING_DIALOG_NOT_ACCEPTED -> 9 // BLE_PAIRING_FAILED_USER_CANCELED
|
|
517
|
+
else -> 3 // UNKNOWN (for any unexpected codes)
|
|
518
|
+
}
|
|
519
|
+
}
|
|
480
520
|
}
|
|
521
|
+
|