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

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.
@@ -0,0 +1,81 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['Flic2_' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ }
16
+ }
17
+
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ apply plugin: "com.facebook.react"
23
+
24
+ def getExtOrIntegerDefault(name) {
25
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Flic2_" + name]).toInteger()
26
+ }
27
+
28
+ android {
29
+ namespace "nl.xguard.flic2"
30
+
31
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
32
+
33
+ defaultConfig {
34
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
35
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
36
+ }
37
+
38
+ buildFeatures {
39
+ buildConfig true
40
+ }
41
+
42
+ buildTypes {
43
+ release {
44
+ minifyEnabled false
45
+ }
46
+ }
47
+
48
+ lintOptions {
49
+ disable "GradleCompatible"
50
+ }
51
+
52
+ compileOptions {
53
+ sourceCompatibility JavaVersion.VERSION_1_8
54
+ targetCompatibility JavaVersion.VERSION_1_8
55
+ }
56
+
57
+ sourceSets {
58
+ main {
59
+ java.srcDirs += [
60
+ "generated/java",
61
+ "generated/jni"
62
+ ]
63
+ }
64
+ }
65
+ }
66
+
67
+ repositories {
68
+ mavenCentral()
69
+ google()
70
+ maven { url 'https://jitpack.io' }
71
+ }
72
+
73
+ def kotlin_version = getExtOrDefault("kotlinVersion")
74
+
75
+ dependencies {
76
+ implementation "com.facebook.react:react-android"
77
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
78
+ implementation 'com.github.50ButtonsEach:flic2lib-android:1.+'
79
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
80
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
81
+ }
@@ -0,0 +1,5 @@
1
+ Flic2_kotlinVersion=2.0.21
2
+ Flic2_minSdkVersion=24
3
+ Flic2_targetSdkVersion=34
4
+ Flic2_compileSdkVersion=35
5
+ Flic2_ndkVersion=27.1.12297006
@@ -0,0 +1,50 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ xmlns:tools="http://schemas.android.com/tools">
3
+
4
+ <!-- Bluetooth permissions for API < 31 -->
5
+ <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
6
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
7
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
8
+
9
+ <!-- Bluetooth permissions for API >= 31 -->
10
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
11
+ android:usesPermissionFlags="neverForLocation"
12
+ tools:targetApi="s" />
13
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
14
+
15
+ <!-- Foreground service permission -->
16
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
17
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
18
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
19
+
20
+ <application>
21
+ <service
22
+ android:name=".Flic2Service"
23
+ android:enabled="true"
24
+ android:exported="false"
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>
48
+ </application>
49
+
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
+
@@ -0,0 +1,144 @@
1
+ package nl.xguard.flic2
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import io.flic.flic2libandroid.BatteryLevel
6
+ import io.flic.flic2libandroid.Flic2Button
7
+ import io.flic.flic2libandroid.Flic2ButtonListener
8
+
9
+ class Flic2ButtonEventListener(
10
+ private val emitEvent: (WritableMap) -> Unit
11
+ ) : Flic2ButtonListener() {
12
+
13
+ // Map Android library's onConnect() to emit "connected" (matches iOS)
14
+ override fun onConnect(button: Flic2Button) {
15
+ emitEvent(createButtonEvent(button, "connected"))
16
+ }
17
+
18
+ // Map Android library's onReady() to emit "ready" (matches iOS)
19
+ override fun onReady(button: Flic2Button, timestamp: Long) {
20
+ emitEvent(createButtonEvent(button, "ready"))
21
+ }
22
+
23
+ // Map Android library's onDisconnect() to emit "disconnected" (matches iOS)
24
+ override fun onDisconnect(button: Flic2Button) {
25
+ emitEvent(createButtonEvent(button, "disconnected"))
26
+ }
27
+
28
+ // Map Android library's onFailure() to emit "connectionFailed" (matches iOS)
29
+ override fun onFailure(button: Flic2Button, errorCode: Int, subCode: Int) {
30
+ emitEvent(createButtonEvent(button, "connectionFailed").apply {
31
+ putMap("error", Arguments.createMap().apply {
32
+ putInt("code", errorCode)
33
+ putInt("subCode", subCode)
34
+ putString("message", "Connection failed: code=$errorCode, subCode=$subCode")
35
+ })
36
+ })
37
+ }
38
+
39
+ override fun onButtonUpOrDown(
40
+ button: Flic2Button,
41
+ wasQueued: Boolean,
42
+ lastQueued: Boolean,
43
+ timestamp: Long,
44
+ isUp: Boolean,
45
+ isDown: Boolean
46
+ ) {
47
+ val event = if (isDown) "buttonDown" else "buttonUp"
48
+ emitEvent(createButtonEvent(button, event).apply {
49
+ putBoolean("queued", wasQueued)
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)
59
+ })
60
+ }
61
+
62
+ // Android library calls ALL applicable callback methods, causing duplicate events.
63
+ // Only onButtonSingleOrDoubleClickOrHold should emit events to match iOS behavior.
64
+ override fun onButtonClickOrHold(
65
+ button: Flic2Button,
66
+ wasQueued: Boolean,
67
+ lastQueued: Boolean,
68
+ timestamp: Long,
69
+ isClick: Boolean,
70
+ isHold: Boolean
71
+ ) {
72
+ // Intentionally empty - events are handled by onButtonSingleOrDoubleClickOrHold
73
+ }
74
+
75
+ // Android library calls ALL applicable callback methods, causing duplicate events.
76
+ // Only onButtonSingleOrDoubleClickOrHold should emit events to match iOS behavior.
77
+ override fun onButtonSingleOrDoubleClick(
78
+ button: Flic2Button,
79
+ wasQueued: Boolean,
80
+ lastQueued: Boolean,
81
+ timestamp: Long,
82
+ isSingleClick: Boolean,
83
+ isDoubleClick: Boolean
84
+ ) {
85
+ // Intentionally empty - events are handled by onButtonSingleOrDoubleClickOrHold
86
+ }
87
+
88
+ override fun onButtonSingleOrDoubleClickOrHold(
89
+ button: Flic2Button,
90
+ wasQueued: Boolean,
91
+ lastQueued: Boolean,
92
+ timestamp: Long,
93
+ isSingleClick: Boolean,
94
+ isDoubleClick: Boolean,
95
+ isHold: Boolean
96
+ ) {
97
+ val event = when {
98
+ isSingleClick -> "click"
99
+ isDoubleClick -> "doubleClick"
100
+ isHold -> "hold"
101
+ else -> "unknown"
102
+ }
103
+ emitEvent(createButtonEvent(button, event).apply {
104
+ putBoolean("queued", wasQueued)
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)
114
+ })
115
+ }
116
+
117
+ // Map Android library's onUnpaired() to emit "unpaired" (matches iOS)
118
+ override fun onUnpaired(button: Flic2Button) {
119
+ emitEvent(createButtonEvent(button, "unpaired"))
120
+ }
121
+
122
+ // Map Android library's onBatteryLevelUpdated() to emit "batteryUpdate" (matches iOS)
123
+ override fun onBatteryLevelUpdated(button: Flic2Button, level: BatteryLevel) {
124
+ emitEvent(createButtonEvent(button, "batteryUpdate").apply {
125
+ putDouble("voltage", level.voltage.toDouble())
126
+ })
127
+ }
128
+
129
+ // Map Android library's onNameUpdated() to emit "nicknameUpdate" (matches iOS)
130
+ override fun onNameUpdated(button: Flic2Button, newName: String) {
131
+ emitEvent(createButtonEvent(button, "nicknameUpdate").apply {
132
+ putString("nickname", newName)
133
+ })
134
+ }
135
+
136
+ private fun createButtonEvent(button: Flic2Button, event: String): WritableMap {
137
+ return Arguments.createMap().apply {
138
+ putString("uuid", button.uuid)
139
+ putString("event", event)
140
+ putMap("button", Flic2Converter.buttonToMap(button))
141
+ }
142
+ }
143
+ }
144
+
@@ -0,0 +1,102 @@
1
+ package nl.xguard.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
+ putBoolean("isReady", connState == Flic2Button.CONNECTION_STATE_CONNECTED_READY)
35
+
36
+ // Get battery level from BatteryLevel object
37
+ val batteryLevel = button.getLastKnownBatteryLevel()
38
+ putDouble("batteryVoltage", batteryLevel?.voltage?.toDouble() ?: 0.0)
39
+
40
+ putBoolean("isUnpaired", button.isUnpaired())
41
+ }
42
+ }
43
+
44
+ fun buttonsToArray(buttons: List<Flic2Button>): WritableArray {
45
+ return Arguments.createArray().apply {
46
+ buttons.forEach { button ->
47
+ pushMap(buttonToMap(button))
48
+ }
49
+ }
50
+ }
51
+
52
+ private fun connectionStateToInt(state: Int): Int {
53
+ return when (state) {
54
+ Flic2Button.CONNECTION_STATE_DISCONNECTED -> 0
55
+ Flic2Button.CONNECTION_STATE_CONNECTING -> 1
56
+ Flic2Button.CONNECTION_STATE_CONNECTED_STARTING -> 2
57
+ Flic2Button.CONNECTION_STATE_CONNECTED_READY -> 3
58
+ else -> 0
59
+ }
60
+ }
61
+
62
+ private fun connectionStateToString(state: Int): String {
63
+ return when (state) {
64
+ Flic2Button.CONNECTION_STATE_DISCONNECTED -> "disconnected"
65
+ Flic2Button.CONNECTION_STATE_CONNECTING -> "connecting"
66
+ Flic2Button.CONNECTION_STATE_CONNECTED_STARTING -> "connected"
67
+ Flic2Button.CONNECTION_STATE_CONNECTED_READY -> "connected"
68
+ else -> "disconnected"
69
+ }
70
+ }
71
+
72
+ // Trigger mode and latency mode are iOS-only features (removed from Android library v1.1.0+)
73
+
74
+ fun scanResultToString(result: Int): String {
75
+ return when (result) {
76
+ 0 -> "success"
77
+ 1 -> "alreadyRunning"
78
+ 2 -> "bluetoothNotActivated"
79
+ 3 -> "unknown"
80
+ 4 -> "noPublicButtonDiscovered"
81
+ 5 -> "alreadyConnectedToAnotherDevice"
82
+ 6 -> "connectionTimeout"
83
+ 7 -> "invalidVerifier"
84
+ 8 -> "blePairingFailedPreviousPairingAlreadyExisting"
85
+ 9 -> "blePairingFailedUserCanceled"
86
+ 10 -> "blePairingFailedUnknownReason"
87
+ 11 -> "appCredentialsDontMatch"
88
+ 12 -> "userCanceled"
89
+ 13 -> "invalidBluetoothAddress"
90
+ 14 -> "genuineCheckFailed"
91
+ 15 -> "tooManyApps"
92
+ 16 -> "couldNotSetBluetoothNotify"
93
+ 17 -> "couldNotDiscoverBluetoothServices"
94
+ 18 -> "buttonDisconnectedDuringVerification"
95
+ 19 -> "failedToEstablish"
96
+ 20 -> "connectionLimitReached"
97
+ 21 -> "notInPublicMode"
98
+ else -> "unknown"
99
+ }
100
+ }
101
+ }
102
+
@@ -0,0 +1,521 @@
1
+ package nl.xguard.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
+ flic2Service = (service as Flic2Service.Flic2ServiceBinder).getService()
52
+ serviceBound = true
53
+
54
+ // Set up listeners for existing buttons
55
+ flic2Service?.getManager()?.let { manager ->
56
+ manager.buttons.forEach { button ->
57
+ setupButtonListener(button)
58
+ }
59
+ // Update foreground service state based on button count
60
+ updateForegroundServiceState(manager.buttons.size)
61
+ }
62
+
63
+ // Resolve the initialize promise if pending
64
+ initializePromise?.let { promise ->
65
+ promise.resolve(null)
66
+ initializePromise = null
67
+ }
68
+ }
69
+
70
+ override fun onServiceDisconnected(name: ComponentName?) {
71
+ Log.d(TAG, "Service disconnected")
72
+ serviceBound = false
73
+ flic2Service = null
74
+
75
+ // Reject any pending initialize promise
76
+ initializePromise?.let { promise ->
77
+ promise.reject("SERVICE_DISCONNECTED", "Service disconnected unexpectedly")
78
+ initializePromise = null
79
+ }
80
+ }
81
+ }
82
+
83
+ override fun getName(): String {
84
+ return NAME
85
+ }
86
+
87
+ override fun invalidate() {
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
+
114
+ moduleScope.cancel()
115
+ if (serviceBound) {
116
+ reactApplicationContext.unbindService(serviceConnection)
117
+ serviceBound = false
118
+ }
119
+ }
120
+
121
+ // MARK: - Manager Methods
122
+
123
+ override fun initialize(background: Boolean, promise: Promise) {
124
+ try {
125
+ // Store the promise to resolve when service is connected
126
+ initializePromise = promise
127
+
128
+ val intent = Intent(reactApplicationContext, Flic2Service::class.java)
129
+
130
+ // Check if service is already running
131
+ if (!ActivityUtil.isServiceRunning(reactApplicationContext, Flic2Service::class.java)) {
132
+ // Start service
133
+ ActivityUtil.startForegroundService(reactApplicationContext, intent)
134
+ }
135
+
136
+ // Bind to service - promise will be resolved in onServiceConnected
137
+ val bound = reactApplicationContext.bindService(
138
+ intent,
139
+ serviceConnection,
140
+ Context.BIND_AUTO_CREATE
141
+ )
142
+
143
+ if (!bound) {
144
+ initializePromise = null
145
+ promise.reject("INIT_ERROR", "Failed to bind to service")
146
+ }
147
+ } catch (e: Exception) {
148
+ Log.e(TAG, "Failed to initialize", e)
149
+ initializePromise = null
150
+ promise.reject("INIT_ERROR", "Failed to initialize: ${e.message}", e)
151
+ }
152
+ }
153
+
154
+ override fun getButtons(promise: Promise) {
155
+ try {
156
+ val manager = flic2Service?.getManager()
157
+ if (manager == null) {
158
+ promise.reject("NOT_INITIALIZED", "Manager not initialized")
159
+ return
160
+ }
161
+
162
+ val buttons = manager.buttons
163
+ val buttonArray = Flic2Converter.buttonsToArray(buttons)
164
+ promise.resolve(buttonArray)
165
+ } catch (e: Exception) {
166
+ Log.e(TAG, "Failed to get buttons", e)
167
+ promise.reject("GET_BUTTONS_ERROR", "Failed to get buttons: ${e.message}", e)
168
+ }
169
+ }
170
+
171
+ override fun scanForButtons(promise: Promise) {
172
+ val manager = flic2Service?.getManager()
173
+ if (manager == null) {
174
+ promise.reject("NOT_INITIALIZED", "Manager not initialized")
175
+ return
176
+ }
177
+
178
+ // Cancel any existing scan
179
+ scanJob?.cancel()
180
+
181
+ Log.d(TAG, "Starting scan")
182
+
183
+ // Emit started event (matches iOS)
184
+ emitOnScanStatusChange(Arguments.createMap().apply {
185
+ putString("event", "started")
186
+ putString("eventName", "started")
187
+ })
188
+
189
+ manager.startScan(object : Flic2ScanCallback {
190
+ override fun onDiscoveredAlreadyPairedButton(button: Flic2Button) {
191
+ Log.d(TAG, "Discovered already paired button")
192
+ }
193
+
194
+ override fun onDiscovered(bdAddr: String) {
195
+ Log.d(TAG, "Discovered button: $bdAddr")
196
+ }
197
+
198
+ override fun onConnected() {
199
+ Log.d(TAG, "Button connected during scan")
200
+ }
201
+
202
+ override fun onComplete(result: Int, subCode: Int, button: Flic2Button?) {
203
+ Log.d(TAG, "Scan complete: result=$result, button=${button?.uuid}")
204
+
205
+ if (result == Flic2ScanCallback.RESULT_SUCCESS && button != null) {
206
+ // Auto-connect (trigger mode not available in Android v1.1.0+)
207
+ button.connect()
208
+
209
+ setupButtonListener(button)
210
+
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)
217
+ emitOnButtonEvent(Arguments.createMap().apply {
218
+ putString("uuid", button.uuid)
219
+ putString("event", "discovered")
220
+ putMap("button", Flic2Converter.buttonToMap(button))
221
+ })
222
+ } else {
223
+ Log.e(TAG, "Scan failed with error code: ${Flic2Converter.scanResultToString(result)}")
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
+ })
232
+ }
233
+ })
234
+
235
+ // Return immediately - scan results will come through events
236
+ promise.resolve(null)
237
+ }
238
+
239
+ override fun stopScan(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
+ scanJob?.cancel()
248
+ manager.stopScan()
249
+
250
+ promise.resolve(null)
251
+ } catch (e: Exception) {
252
+ Log.e(TAG, "Failed to stop scan", e)
253
+ promise.reject("STOP_SCAN_ERROR", "Failed to stop scan: ${e.message}", e)
254
+ }
255
+ }
256
+
257
+ override fun forgetButton(uuid: String, promise: Promise) {
258
+ try {
259
+ val manager = flic2Service?.getManager()
260
+ if (manager == null) {
261
+ promise.reject("NOT_INITIALIZED", "Manager not initialized")
262
+ return
263
+ }
264
+
265
+ val button = manager.buttons.find { it.uuid == uuid }
266
+ if (button == null) {
267
+ promise.reject("BUTTON_NOT_FOUND", "Button not found")
268
+ return
269
+ }
270
+
271
+ // Disconnect before forgetting like iOS
272
+ button.disconnectOrAbortPendingConnection()
273
+
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
286
+ buttonListeners.remove(uuid)
287
+
288
+ // Forget button
289
+ manager.forgetButton(button)
290
+
291
+ // Update foreground service state after removing button
292
+ updateForegroundServiceState(manager.buttons.size)
293
+
294
+ promise.resolve(null)
295
+ } catch (e: Exception) {
296
+ Log.e(TAG, "Failed to forget button", e)
297
+ promise.reject("FORGET_ERROR", "Failed to forget button: ${e.message}", e)
298
+ }
299
+ }
300
+
301
+ // MARK: - Button Methods
302
+
303
+ override fun connectButton(uuid: String, promise: Promise) {
304
+ try {
305
+ val button = findButton(uuid)
306
+ if (button == null) {
307
+ promise.reject("BUTTON_NOT_FOUND", "Button not found")
308
+ return
309
+ }
310
+
311
+ button.connect()
312
+
313
+ promise.resolve(Flic2Converter.buttonToMap(button))
314
+ } catch (e: Exception) {
315
+ Log.e(TAG, "Failed to connect button", e)
316
+ promise.reject("CONNECT_ERROR", "Failed to connect: ${e.message}", e)
317
+ }
318
+ }
319
+
320
+ override fun disconnectButton(uuid: String, promise: Promise) {
321
+ try {
322
+ val button = findButton(uuid)
323
+ if (button == null) {
324
+ promise.reject("BUTTON_NOT_FOUND", "Button not found")
325
+ return
326
+ }
327
+
328
+ button.disconnectOrAbortPendingConnection()
329
+
330
+ promise.resolve(Flic2Converter.buttonToMap(button))
331
+ } catch (e: Exception) {
332
+ Log.e(TAG, "Failed to disconnect button", e)
333
+ promise.reject("DISCONNECT_ERROR", "Failed to disconnect: ${e.message}", e)
334
+ }
335
+ }
336
+
337
+ override fun setTriggerMode(uuid: String, mode: Double, promise: Promise) {
338
+ promise.reject(
339
+ "NOT_SUPPORTED_ON_ANDROID",
340
+ "Trigger mode is only supported on iOS. Android Flic2 library v1.1.0+ does not support trigger modes."
341
+ )
342
+ }
343
+
344
+ override fun setLatencyMode(uuid: String, mode: Double, promise: Promise) {
345
+ promise.reject(
346
+ "NOT_SUPPORTED_ON_ANDROID",
347
+ "Latency mode is only supported on iOS. Android Flic2 library v1.1.0+ does not support latency modes."
348
+ )
349
+ }
350
+
351
+ override fun setNickname(uuid: String, nickname: String, promise: Promise) {
352
+ try {
353
+ val button = findButton(uuid)
354
+ if (button == null) {
355
+ promise.reject("BUTTON_NOT_FOUND", "Button not found")
356
+ return
357
+ }
358
+
359
+ // v1.1.0 uses setName() method instead of property
360
+ button.setName(nickname)
361
+
362
+ promise.resolve(Flic2Converter.buttonToMap(button))
363
+ } catch (e: Exception) {
364
+ Log.e(TAG, "Failed to set nickname", e)
365
+ promise.reject("SET_NICKNAME_ERROR", "Failed to set nickname: ${e.message}", e)
366
+ }
367
+ }
368
+
369
+ override fun connectAllKnownButtons(promise: Promise) {
370
+ try {
371
+ val manager = flic2Service?.getManager()
372
+ if (manager == null) {
373
+ promise.reject("NOT_INITIALIZED", "Manager not initialized")
374
+ return
375
+ }
376
+
377
+ val buttons = manager.buttons
378
+
379
+ buttons.forEach { button ->
380
+ Log.d(TAG, "Connecting button: ${button.getName()}")
381
+ // Trigger mode not available in Android v1.1.0+
382
+ button.connect()
383
+ }
384
+
385
+ promise.resolve(null)
386
+ } catch (e: Exception) {
387
+ Log.e(TAG, "Failed to connect all buttons", e)
388
+ promise.reject("CONNECT_ALL_ERROR", "Failed to connect all buttons: ${e.message}", e)
389
+ }
390
+ }
391
+
392
+ override fun disconnectAllKnownButtons(promise: Promise) {
393
+ try {
394
+ val manager = flic2Service?.getManager()
395
+ if (manager == null) {
396
+ promise.reject("NOT_INITIALIZED", "Manager not initialized")
397
+ return
398
+ }
399
+
400
+ val buttons = manager.buttons
401
+
402
+ buttons.forEach { button ->
403
+ Log.d(TAG, "Disconnecting button: ${button.name}")
404
+ button.disconnectOrAbortPendingConnection()
405
+ }
406
+
407
+ promise.resolve(null)
408
+ } catch (e: Exception) {
409
+ Log.e(TAG, "Failed to disconnect all buttons", e)
410
+ promise.reject("DISCONNECT_ALL_ERROR", "Failed to disconnect all buttons: ${e.message}", e)
411
+ }
412
+ }
413
+
414
+ override fun forgetAllButtons(promise: Promise) {
415
+ try {
416
+ val manager = flic2Service?.getManager()
417
+ if (manager == null) {
418
+ promise.reject("NOT_INITIALIZED", "Manager not initialized")
419
+ return
420
+ }
421
+
422
+ // Create a copy of the list to avoid concurrent modification
423
+ val buttons = manager.buttons.toList()
424
+
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
438
+ buttonListeners.remove(button.uuid)
439
+
440
+ // Disconnect before forgetting
441
+ button.disconnectOrAbortPendingConnection()
442
+
443
+ // Forget button
444
+ manager.forgetButton(button)
445
+ }
446
+
447
+ // Update foreground service state after removing all buttons
448
+ updateForegroundServiceState(manager.buttons.size)
449
+
450
+ promise.resolve(null)
451
+ } catch (e: Exception) {
452
+ Log.e(TAG, "Failed to forget all buttons", e)
453
+ promise.reject("FORGET_ALL_ERROR", "Failed to forget all buttons: ${e.message}", e)
454
+ }
455
+ }
456
+
457
+ override fun isScanning(promise: Promise) {
458
+ try {
459
+ val manager = flic2Service?.getManager()
460
+ if (manager == null) {
461
+ promise.reject("NOT_INITIALIZED", "Manager not initialized")
462
+ return
463
+ }
464
+
465
+ promise.resolve(scanJob != null && scanJob?.isActive == true)
466
+ } catch (e: Exception) {
467
+ Log.e(TAG, "Failed to check scanning status", e)
468
+ promise.reject("IS_SCANNING_ERROR", "Failed to check scanning status: ${e.message}", e)
469
+ }
470
+ }
471
+
472
+ // MARK: - Helper Methods
473
+
474
+ private fun findButton(uuid: String): Flic2Button? {
475
+ return flic2Service?.getManager()?.buttons?.find { it.uuid == uuid }
476
+ }
477
+
478
+ private fun setupButtonListener(button: Flic2Button) {
479
+ // Remove existing listener if any
480
+ buttonListeners.remove(button.uuid)
481
+
482
+ // Create new listener
483
+ val listener = Flic2ButtonEventListener { event ->
484
+ emitOnButtonEvent(event)
485
+ }
486
+
487
+ // Add listener to button
488
+ button.addListener(listener)
489
+
490
+ // Store listener reference
491
+ buttonListeners[button.uuid] = listener
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
+ }
520
+ }
521
+
@@ -0,0 +1,34 @@
1
+ package nl.xguard.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
+ }
34
+
@@ -0,0 +1,274 @@
1
+ package nl.xguard.flic2
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.app.PendingIntent
7
+ import android.app.Service
8
+ import android.content.BroadcastReceiver
9
+ import android.content.Context
10
+ import android.content.Intent
11
+ import android.content.pm.PackageManager
12
+ import android.os.Binder
13
+ import android.os.Build
14
+ import android.os.Handler
15
+ import android.os.IBinder
16
+ import android.os.Looper
17
+ import android.util.Log
18
+ import androidx.core.app.NotificationCompat
19
+ import io.flic.flic2libandroid.Flic2Manager
20
+
21
+ class Flic2Service : Service() {
22
+
23
+ private val binder = Flic2ServiceBinder()
24
+ private var manager: Flic2Manager? = null
25
+ private var isServiceStarted = false
26
+ private var notification: Notification? = null
27
+
28
+ companion object {
29
+ private const val TAG = "Flic2Service"
30
+ private const val DEFAULT_NOTIFICATION_ID = 123321
31
+ private const val DEFAULT_CHANNEL_ID = "Notification_Channel_Flic2Service"
32
+
33
+ // Metadata keys for notification configuration
34
+ private const val KEY_CHANNEL_NAME = "nl.xguard.flic2.notification_channel_name"
35
+ private const val KEY_CHANNEL_DESCRIPTION = "nl.xguard.flic2.notification_channel_description"
36
+ private const val NOTIFICATION_TITLE_KEY = "nl.xguard.flic2.notification_title"
37
+ private const val NOTIFICATION_TEXT_KEY = "nl.xguard.flic2.notification_text"
38
+ private const val NOTIFICATION_ICON_KEY = "nl.xguard.flic2.notification_icon"
39
+ private const val NOTIFICATION_ID_KEY = "nl.xguard.flic2.notification_id"
40
+ private const val CHANNEL_ID_KEY = "nl.xguard.flic2.notification_channel_id"
41
+ }
42
+
43
+ inner class Flic2ServiceBinder : Binder() {
44
+ fun getService(): Flic2Service = this@Flic2Service
45
+ }
46
+
47
+ override fun onCreate() {
48
+ super.onCreate()
49
+ Log.d(TAG, "Service onCreate")
50
+
51
+ try {
52
+ // Initialize Flic2Manager on main thread with Handler
53
+ // v1.1.0 API: init() returns void, must call getInstance() after
54
+ Flic2Manager.init(
55
+ applicationContext,
56
+ Handler(Looper.getMainLooper())
57
+ )
58
+ manager = Flic2Manager.getInstance()
59
+ Log.d(TAG, "Flic2Manager initialized successfully")
60
+ } catch (e: Exception) {
61
+ Log.e(TAG, "Failed to initialize Flic2Manager", e)
62
+ }
63
+
64
+ // Create notification channel and notification in onCreate
65
+ createNotificationChannel()
66
+ notification = createNotification()
67
+ }
68
+
69
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
70
+ Log.d(TAG, "Service onStartCommand")
71
+
72
+ if (intent != null) {
73
+ if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
74
+ Log.d(TAG, "onStartCommand: ACTION_BOOT_COMPLETED")
75
+ }
76
+ }
77
+
78
+ // Start foreground service if notification is ready
79
+ if (notification != null) {
80
+ startForegroundService()
81
+ }
82
+
83
+ return START_STICKY
84
+ }
85
+
86
+ override fun onBind(intent: Intent?): IBinder {
87
+ Log.d(TAG, "Service onBind")
88
+ return binder
89
+ }
90
+
91
+ override fun onDestroy() {
92
+ Log.d(TAG, "Service onDestroy")
93
+ super.onDestroy()
94
+ }
95
+
96
+ fun getManager(): Flic2Manager? = manager
97
+
98
+ fun isManagerInitialized(): Boolean = manager != null
99
+
100
+ fun startForegroundService() {
101
+ if (!isServiceStarted && notification != null) {
102
+ isServiceStarted = true
103
+ try {
104
+ startForeground(getNotificationId(), notification)
105
+ } catch (e: Exception) {
106
+ Log.w(TAG, "startForegroundService() exception", e)
107
+ }
108
+ }
109
+ }
110
+
111
+ fun stopForegroundService() {
112
+ if (isServiceStarted) {
113
+ isServiceStarted = false
114
+ stopForeground(true)
115
+ }
116
+ }
117
+
118
+ private fun createNotificationChannel() {
119
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
120
+ val channel = NotificationChannel(
121
+ getChannelId(),
122
+ getChannelName(),
123
+ NotificationManager.IMPORTANCE_LOW
124
+ ).apply {
125
+ description = getChannelDescription()
126
+ setShowBadge(false)
127
+ }
128
+
129
+ getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
130
+ }
131
+ }
132
+
133
+ private fun createNotification(): Notification {
134
+ val notificationIntent = Intent(this, Flic2Service::class.java)
135
+ val pendingIntent = PendingIntent.getActivity(
136
+ this,
137
+ 0,
138
+ notificationIntent,
139
+ PendingIntent.FLAG_IMMUTABLE
140
+ )
141
+
142
+ return NotificationCompat.Builder(this, getChannelId())
143
+ .setContentTitle(getNotificationTitle())
144
+ .setContentText(getNotificationText())
145
+ .setSmallIcon(getNotificationIcon())
146
+ .setContentIntent(pendingIntent)
147
+ .setPriority(NotificationCompat.PRIORITY_LOW)
148
+ .setOngoing(true)
149
+ .build()
150
+ }
151
+
152
+ private fun getNotificationId(): Int {
153
+ return try {
154
+ val metadata = applicationContext.packageManager
155
+ .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
156
+ .metaData
157
+ metadata?.getInt(NOTIFICATION_ID_KEY, DEFAULT_NOTIFICATION_ID) ?: DEFAULT_NOTIFICATION_ID
158
+ } catch (e: PackageManager.NameNotFoundException) {
159
+ Log.w(TAG, "getNotificationId() NameNotFoundException", e)
160
+ DEFAULT_NOTIFICATION_ID
161
+ } catch (e: Exception) {
162
+ Log.w(TAG, "getNotificationId() exception", e)
163
+ DEFAULT_NOTIFICATION_ID
164
+ }
165
+ }
166
+
167
+ private fun getChannelId(): String {
168
+ return try {
169
+ val metadata = applicationContext.packageManager
170
+ .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
171
+ .metaData
172
+ metadata?.getString(CHANNEL_ID_KEY) ?: DEFAULT_CHANNEL_ID
173
+ } catch (e: PackageManager.NameNotFoundException) {
174
+ Log.w(TAG, "getChannelId() NameNotFoundException", e)
175
+ DEFAULT_CHANNEL_ID
176
+ } catch (e: Exception) {
177
+ Log.w(TAG, "getChannelId() exception", e)
178
+ DEFAULT_CHANNEL_ID
179
+ }
180
+ }
181
+
182
+ private fun getChannelName(): String {
183
+ return try {
184
+ val metadata = applicationContext.packageManager
185
+ .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
186
+ .metaData
187
+ metadata?.getString(KEY_CHANNEL_NAME) ?: "Flic2Channel"
188
+ } catch (e: PackageManager.NameNotFoundException) {
189
+ Log.w(TAG, "getChannelName() NameNotFoundException", e)
190
+ "Flic2Channel"
191
+ } catch (e: Exception) {
192
+ Log.w(TAG, "getChannelName() exception", e)
193
+ "Flic2Channel"
194
+ }
195
+ }
196
+
197
+ private fun getChannelDescription(): String {
198
+ return try {
199
+ val metadata = applicationContext.packageManager
200
+ .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
201
+ .metaData
202
+ metadata?.getString(KEY_CHANNEL_DESCRIPTION) ?: "Flic2Channel"
203
+ } catch (e: PackageManager.NameNotFoundException) {
204
+ Log.w(TAG, "getChannelDescription() NameNotFoundException", e)
205
+ "Flic2Channel"
206
+ } catch (e: Exception) {
207
+ Log.w(TAG, "getChannelDescription() exception", e)
208
+ "Flic2Channel"
209
+ }
210
+ }
211
+
212
+ private fun getNotificationTitle(): String {
213
+ return try {
214
+ val metadata = applicationContext.packageManager
215
+ .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
216
+ .metaData
217
+ metadata?.getString(NOTIFICATION_TITLE_KEY) ?: "Flic 2"
218
+ } catch (e: PackageManager.NameNotFoundException) {
219
+ Log.w(TAG, "getNotificationTitle() NameNotFoundException", e)
220
+ "Flic 2"
221
+ } catch (e: Exception) {
222
+ Log.w(TAG, "getNotificationTitle() exception", e)
223
+ "Flic 2"
224
+ }
225
+ }
226
+
227
+ private fun getNotificationText(): String {
228
+ return try {
229
+ val metadata = applicationContext.packageManager
230
+ .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
231
+ .metaData
232
+ metadata?.getString(NOTIFICATION_TEXT_KEY) ?: "Flic 2 service is running"
233
+ } catch (e: PackageManager.NameNotFoundException) {
234
+ Log.w(TAG, "getNotificationText() NameNotFoundException", e)
235
+ "Flic 2 service is running"
236
+ } catch (e: Exception) {
237
+ Log.w(TAG, "getNotificationText() exception", e)
238
+ "Flic 2 service is running"
239
+ }
240
+ }
241
+
242
+ private fun getNotificationIcon(): Int {
243
+ return try {
244
+ val metadata = applicationContext.packageManager
245
+ .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
246
+ .metaData
247
+ val icon = metadata?.getInt(NOTIFICATION_ICON_KEY, 0) ?: 0
248
+ if (icon != 0) icon else android.R.drawable.ic_dialog_info
249
+ } catch (e: PackageManager.NameNotFoundException) {
250
+ Log.w(TAG, "getNotificationIcon() NameNotFoundException", e)
251
+ android.R.drawable.ic_dialog_info
252
+ } catch (e: Exception) {
253
+ Log.w(TAG, "getNotificationIcon() exception", e)
254
+ android.R.drawable.ic_dialog_info
255
+ }
256
+ }
257
+
258
+ // BootUpReceiver for handling device boot
259
+ class BootUpReceiver : BroadcastReceiver() {
260
+ override fun onReceive(context: Context, intent: Intent) {
261
+ Log.d(TAG, "BootUpReceiver()")
262
+ // The Application class's onCreate has already been called at this point, which is what we want
263
+ }
264
+ }
265
+
266
+ // UpdateReceiver for handling app updates
267
+ class UpdateReceiver : BroadcastReceiver() {
268
+ override fun onReceive(context: Context, intent: Intent) {
269
+ Log.d(TAG, "UpdateReceiver()")
270
+ // The Application class's onCreate has already been called at this point, which is what we want
271
+ }
272
+ }
273
+ }
274
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-flic2",
3
- "version": "2.0.0-beta.11",
3
+ "version": "2.0.0-beta.13",
4
4
  "description": "React Native Flic plugin made with the Flic2 SDK (unofficial)",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",