expo-flic2 0.2.0 → 0.2.2

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.
@@ -23,7 +23,8 @@
23
23
  "Bash(echo ANDROID_HOME=$ANDROID_HOME)",
24
24
  "Read(//Users/ziller321/code/expo-flic2/$ANDROID_HOME/**)",
25
25
  "Bash(git add:*)",
26
- "Bash(git commit:*)"
26
+ "Bash(git commit:*)",
27
+ "Bash(git push:*)"
27
28
  ]
28
29
  }
29
30
  }
package/CLAUDE.md ADDED
@@ -0,0 +1,29 @@
1
+ # expo-flic2
2
+
3
+ ## Versioning
4
+
5
+ When releasing a new version, update the version string in **three places**:
6
+
7
+ ### 1. `package.json` (source of truth)
8
+ ```json
9
+ "version": "X.Y.Z"
10
+ ```
11
+ This also drives the **iOS** version — `ios/ExpoFlic2.podspec` reads `package['version']` directly, so no separate iOS change is needed.
12
+
13
+ ### 2. `android/build.gradle` (two places)
14
+ ```groovy
15
+ version = 'X.Y.Z' // top-level, used by Gradle/Maven
16
+ ...
17
+ defaultConfig {
18
+ versionName "X.Y.Z" // required by expo-module-gradle-plugin
19
+ }
20
+ ```
21
+
22
+ ### Summary table
23
+
24
+ | File | Field | Notes |
25
+ |------|-------|-------|
26
+ | `package.json` | `"version"` | Drives npm publish and iOS podspec |
27
+ | `android/build.gradle` | `version = '...'` | Gradle/Maven artifact version |
28
+ | `android/build.gradle` | `defaultConfig.versionName` | Required by expo-module-gradle-plugin |
29
+ | `ios/ExpoFlic2.podspec` | `s.version` | **Auto-read from package.json — do not edit** |
package/README.md CHANGED
@@ -1,35 +1,427 @@
1
1
  # expo-flic2
2
2
 
3
- Expo module for Flic2 Bluetooth buttons
3
+ Expo module for integrating [Flic2](https://flic.io/) Bluetooth buttons into your React Native / Expo app.
4
4
 
5
- # API documentation
5
+ Supports **iOS** and **Android**. Provides a typed API for scanning, connecting, and reacting to button events (click, double-click, hold, and press/release).
6
6
 
7
- - [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/flic2/)
8
- - [Documentation for the main branch](https://docs.expo.dev/versions/unversioned/sdk/flic2/)
7
+ ## Installation
9
8
 
10
- # Installation in managed Expo projects
9
+ ```sh
10
+ npm install expo-flic2
11
+ ```
11
12
 
12
- For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](#api-documentation). If you follow the link and there is no documentation available then this library is not yet usable within managed projects — it is likely to be included in an upcoming Expo SDK release.
13
+ ### iOS
13
14
 
14
- # Installation in bare React Native projects
15
+ Run `npx pod-install` after installing.
15
16
 
16
- For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
17
+ Add Bluetooth usage descriptions to your `app.json` (or `app.config.js`) the config plugin handles this automatically when using Expo managed workflow:
18
+
19
+ ```json
20
+ {
21
+ "expo": {
22
+ "plugins": ["expo-flic2"]
23
+ }
24
+ }
25
+ ```
17
26
 
18
- ### Add the package to your npm dependencies
27
+ For bare React Native projects, add to `ios/MyApp/Info.plist`:
19
28
 
29
+ ```xml
30
+ <key>NSBluetoothAlwaysUsageDescription</key>
31
+ <string>This app uses Bluetooth to connect to Flic2 buttons.</string>
32
+ <key>NSBluetoothPeripheralUsageDescription</key>
33
+ <string>This app uses Bluetooth to connect to Flic2 buttons.</string>
20
34
  ```
21
- npm install expo-flic2
35
+
36
+ ### Android
37
+
38
+ The module requires the following permissions in `AndroidManifest.xml`:
39
+
40
+ ```xml
41
+ <uses-permission android:name="android.permission.BLUETOOTH" />
42
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
43
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
44
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
45
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
46
+ ```
47
+
48
+ The config plugin adds these automatically for managed Expo projects.
49
+
50
+ ## Quick Start
51
+
52
+ ```typescript
53
+ import { useEffect } from "react";
54
+ import {
55
+ initialize,
56
+ startScan,
57
+ stopScan,
58
+ addOnScanListener,
59
+ addOnClickListener,
60
+ addOnConnectionListener,
61
+ connectButton,
62
+ } from "expo-flic2";
63
+
64
+ export default function App() {
65
+ useEffect(() => {
66
+ // 1. Initialize the Flic2 Bluetooth manager
67
+ initialize();
68
+
69
+ // 2. Listen for button events
70
+ const clickSub = addOnClickListener(({ uuid, queued, age }) => {
71
+ console.log(`Button ${uuid} clicked (age: ${age}ms)`);
72
+ });
73
+
74
+ const connSub = addOnConnectionListener(({ uuid, state, error }) => {
75
+ console.log(`Button ${uuid} connection state: ${state}`);
76
+ if (error) console.error("Connection error:", error);
77
+ });
78
+
79
+ // 3. Scan for nearby buttons
80
+ const scanSub = addOnScanListener(({ isScanning, button, error }) => {
81
+ if (error) {
82
+ console.error("Scan error:", error);
83
+ return;
84
+ }
85
+ if (button) {
86
+ console.log("Discovered button:", button.name, button.uuid);
87
+ // Connect as soon as it's found
88
+ connectButton(button.uuid);
89
+ }
90
+ });
91
+
92
+ startScan();
93
+
94
+ return () => {
95
+ clickSub.remove();
96
+ connSub.remove();
97
+ scanSub.remove();
98
+ stopScan();
99
+ };
100
+ }, []);
101
+
102
+ return <YourAppUI />;
103
+ }
104
+ ```
105
+
106
+ ## API
107
+
108
+ ### Initialization
109
+
110
+ #### `initialize()`
111
+
112
+ Initialize the Flic2 Bluetooth manager. Call this once before using any other API, typically on app startup.
113
+
114
+ ```typescript
115
+ import { initialize } from "expo-flic2";
116
+
117
+ initialize();
118
+ ```
119
+
120
+ ### Scanning
121
+
122
+ #### `startScan()`
123
+
124
+ Start scanning for nearby Flic2 buttons. Fires `onFlic2Scan` events as buttons are discovered.
125
+
126
+ #### `stopScan()`
127
+
128
+ Stop the active scan.
129
+
130
+ ```typescript
131
+ import { startScan, stopScan, addOnScanListener } from "expo-flic2";
132
+
133
+ const sub = addOnScanListener(({ isScanning, button, error, scanEvent }) => {
134
+ if (button) {
135
+ console.log("Found:", button.name, button.serialNumber);
136
+ }
137
+ if (!isScanning) {
138
+ console.log("Scan stopped");
139
+ }
140
+ });
141
+
142
+ startScan();
143
+
144
+ // Later...
145
+ stopScan();
146
+ sub.remove();
147
+ ```
148
+
149
+ ### Button Management
150
+
151
+ #### `getButtons(): Flic2Button[]`
152
+
153
+ Returns all known (previously paired or discovered) buttons.
154
+
155
+ ```typescript
156
+ import { getButtons } from "expo-flic2";
157
+
158
+ const buttons = getButtons();
159
+ buttons.forEach((btn) => {
160
+ console.log(btn.name, btn.connectionState, btn.batteryLevel);
161
+ });
162
+ ```
163
+
164
+ #### `connectButton(uuid: string)`
165
+
166
+ Connect to a button by its UUID.
167
+
168
+ #### `disconnectButton(uuid: string)`
169
+
170
+ Disconnect from a button without removing it from the known list.
171
+
172
+ #### `forgetButton(uuid: string)`
173
+
174
+ Disconnect and remove a button from the known list.
175
+
176
+ ```typescript
177
+ import { connectButton, disconnectButton, forgetButton } from "expo-flic2";
178
+
179
+ connectButton("some-uuid");
180
+ disconnectButton("some-uuid");
181
+ forgetButton("some-uuid"); // removes it entirely
182
+ ```
183
+
184
+ #### `setButtonTriggerMode(uuid: string, mode: Flic2TriggerMode)`
185
+
186
+ Configure which gesture events a button fires. Use this to reduce latency — for example, if you only need single clicks, set `Click` mode so the button doesn't wait to rule out a double-click.
187
+
188
+ ```typescript
189
+ import { setButtonTriggerMode, Flic2TriggerMode } from "expo-flic2";
190
+
191
+ // Only fire click events
192
+ setButtonTriggerMode(uuid, Flic2TriggerMode.Click);
193
+
194
+ // Fire click and double-click events
195
+ setButtonTriggerMode(uuid, Flic2TriggerMode.ClickAndDoubleClick);
196
+
197
+ // Fire click and hold events
198
+ setButtonTriggerMode(uuid, Flic2TriggerMode.ClickAndHold);
199
+
200
+ // Fire all event types
201
+ setButtonTriggerMode(uuid, Flic2TriggerMode.ClickAndDoubleClickAndHold);
202
+ ```
203
+
204
+ ### Event Listeners
205
+
206
+ All listeners return an `EventSubscription` — call `.remove()` to unsubscribe.
207
+
208
+ #### `addOnClickListener(listener)`
209
+
210
+ Fires when a button is single-clicked.
211
+
212
+ ```typescript
213
+ const sub = addOnClickListener(({ uuid, queued, age }) => {
214
+ // queued: true if the event was stored locally while disconnected
215
+ // age: how many ms ago the event occurred
216
+ console.log(`Clicked: ${uuid}`);
217
+ });
218
+ ```
219
+
220
+ #### `addOnDoubleClickListener(listener)`
221
+
222
+ Fires when a button is double-clicked. Requires `Flic2TriggerMode.ClickAndDoubleClick` or `ClickAndDoubleClickAndHold`.
223
+
224
+ ```typescript
225
+ const sub = addOnDoubleClickListener(({ uuid, queued, age }) => {
226
+ console.log(`Double-clicked: ${uuid}`);
227
+ });
228
+ ```
229
+
230
+ #### `addOnHoldListener(listener)`
231
+
232
+ Fires when a button is held. Requires `Flic2TriggerMode.ClickAndHold` or `ClickAndDoubleClickAndHold`.
233
+
234
+ ```typescript
235
+ const sub = addOnHoldListener(({ uuid, queued, age }) => {
236
+ console.log(`Held: ${uuid}`);
237
+ });
238
+ ```
239
+
240
+ #### `addOnUpOrDownListener(listener)`
241
+
242
+ Fires on every press and release, regardless of trigger mode.
243
+
244
+ ```typescript
245
+ const sub = addOnUpOrDownListener(({ uuid, isDown, queued, age }) => {
246
+ console.log(`Button ${uuid} ${isDown ? "pressed" : "released"}`);
247
+ });
248
+ ```
249
+
250
+ #### `addOnConnectionListener(listener)`
251
+
252
+ Fires when a button's connection state changes.
253
+
254
+ ```typescript
255
+ import { addOnConnectionListener, Flic2ConnectionState } from "expo-flic2";
256
+
257
+ const sub = addOnConnectionListener(({ uuid, state, error }) => {
258
+ if (state === Flic2ConnectionState.Ready) {
259
+ console.log(`${uuid} is ready to use`);
260
+ } else if (state === Flic2ConnectionState.Disconnected && error) {
261
+ console.error(`${uuid} disconnected with error: ${error}`);
262
+ }
263
+ });
264
+ ```
265
+
266
+ #### `addOnScanListener(listener)`
267
+
268
+ Fires during scanning with discovery updates.
269
+
270
+ ```typescript
271
+ const sub = addOnScanListener(({ isScanning, button, error, scanEvent }) => {
272
+ // scanEvent (iOS only): "discovered" | "connected" | "verified" | "verificationFailed"
273
+ if (button) console.log("Discovered:", button.name);
274
+ if (error) console.error("Scan error:", error);
275
+ });
276
+ ```
277
+
278
+ #### `addOnBatteryListener(listener)`
279
+
280
+ Fires when a button reports a battery level update.
281
+
282
+ ```typescript
283
+ const sub = addOnBatteryListener(({ uuid, level }) => {
284
+ console.log(`Battery for ${uuid}: ${level}%`);
285
+ if (level < 20) {
286
+ alert("Flic2 battery is low!");
287
+ }
288
+ });
22
289
  ```
23
290
 
24
- ### Configure for Android
291
+ #### `addOnManagerStateListener(listener)`
25
292
 
293
+ Fires when the device's Bluetooth state changes.
294
+
295
+ ```typescript
296
+ const sub = addOnManagerStateListener(({ state }) => {
297
+ if (state === "poweredOff") {
298
+ alert("Please enable Bluetooth to use Flic2 buttons.");
299
+ }
300
+ });
301
+ ```
26
302
 
303
+ ## Types
27
304
 
305
+ ```typescript
306
+ type Flic2Button = {
307
+ uuid: string;
308
+ bluetoothAddress: string;
309
+ serialNumber: string;
310
+ name: string;
311
+ connectionState: Flic2ConnectionState;
312
+ firmwareVersion: number;
313
+ batteryLevel: number;
314
+ pressCount: number;
315
+ triggerMode: Flic2TriggerMode;
316
+ isReady: boolean;
317
+ };
318
+
319
+ enum Flic2ConnectionState {
320
+ Disconnected = "disconnected",
321
+ Connecting = "connecting",
322
+ Connected = "connected",
323
+ Ready = "ready",
324
+ }
325
+
326
+ enum Flic2TriggerMode {
327
+ Click = "click",
328
+ ClickAndHold = "clickAndHold",
329
+ ClickAndDoubleClick = "clickAndDoubleClick",
330
+ ClickAndDoubleClickAndHold = "clickAndDoubleClickAndHold",
331
+ }
332
+ ```
28
333
 
29
- ### Configure for iOS
334
+ ## Complete Example: Button Controller Hook
30
335
 
31
- Run `npx pod-install` after installing the npm package.
336
+ ```typescript
337
+ import { useEffect, useState } from "react";
338
+ import {
339
+ initialize,
340
+ startScan,
341
+ stopScan,
342
+ getButtons,
343
+ connectButton,
344
+ setButtonTriggerMode,
345
+ addOnManagerStateListener,
346
+ addOnScanListener,
347
+ addOnConnectionListener,
348
+ addOnClickListener,
349
+ addOnDoubleClickListener,
350
+ addOnHoldListener,
351
+ Flic2Button,
352
+ Flic2TriggerMode,
353
+ Flic2ConnectionState,
354
+ } from "expo-flic2";
355
+
356
+ export function useFlic2() {
357
+ const [buttons, setButtons] = useState<Flic2Button[]>([]);
358
+ const [isScanning, setIsScanning] = useState(false);
359
+ const [bluetoothReady, setBluetoothReady] = useState(false);
360
+
361
+ useEffect(() => {
362
+ initialize();
363
+
364
+ const managerSub = addOnManagerStateListener(({ state }) => {
365
+ setBluetoothReady(state === "poweredOn");
366
+ });
367
+
368
+ const scanSub = addOnScanListener(({ isScanning, button }) => {
369
+ setIsScanning(isScanning);
370
+ if (button) {
371
+ connectButton(button.uuid);
372
+ setButtonTriggerMode(button.uuid, Flic2TriggerMode.ClickAndDoubleClickAndHold);
373
+ }
374
+ });
375
+
376
+ const connSub = addOnConnectionListener(({ state }) => {
377
+ if (state === Flic2ConnectionState.Ready) {
378
+ setButtons(getButtons());
379
+ }
380
+ });
381
+
382
+ return () => {
383
+ managerSub.remove();
384
+ scanSub.remove();
385
+ connSub.remove();
386
+ };
387
+ }, []);
388
+
389
+ const scan = () => {
390
+ setIsScanning(true);
391
+ startScan();
392
+ // Auto-stop after 10 seconds
393
+ setTimeout(() => stopScan(), 10_000);
394
+ };
395
+
396
+ return { buttons, isScanning, bluetoothReady, scan };
397
+ }
398
+
399
+ // Usage in a component:
400
+ function FlicController() {
401
+ const { buttons, isScanning, bluetoothReady, scan } = useFlic2();
402
+
403
+ useEffect(() => {
404
+ const clickSub = addOnClickListener(({ uuid }) => {
405
+ console.log("Click from", uuid);
406
+ });
407
+ const doubleSub = addOnDoubleClickListener(({ uuid }) => {
408
+ console.log("Double-click from", uuid);
409
+ });
410
+ const holdSub = addOnHoldListener(({ uuid }) => {
411
+ console.log("Hold from", uuid);
412
+ });
413
+
414
+ return () => {
415
+ clickSub.remove();
416
+ doubleSub.remove();
417
+ holdSub.remove();
418
+ };
419
+ }, []);
420
+
421
+ // ...
422
+ }
423
+ ```
32
424
 
33
- # Contributing
425
+ ## Contributing
34
426
 
35
- Contributions are very welcome! Please refer to guidelines described in the [contributing guide]( https://github.com/expo/expo#contributing).
427
+ Contributions are welcome! Please refer to the [contributing guide](https://github.com/expo/expo#contributing).
@@ -4,12 +4,14 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'expo.modules.flic2'
7
- version = '0.1.0'
7
+ version = '0.2.2'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.flic2"
11
11
 
12
12
  defaultConfig {
13
+ versionCode 1
14
+ versionName "0.2.2"
13
15
  minSdkVersion 24
14
16
  }
15
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-flic2",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Expo module for Flic2 Bluetooth buttons",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",