@craft-native/android 0.0.89 → 0.0.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,14 +50,14 @@ Build native Android apps with web technologies using Craft.
50
50
 
51
51
  - **Device Info** - Device model, OS version, screen info
52
52
  - **Network Status** - Connection type monitoring (WiFi/Cellular)
53
- - **App Badge** - Notification badge count
53
+ - **Capability Reporting** - Unsupported app badges are reported honestly
54
54
  - **App State** - Foreground/background detection
55
55
  - **Flashlight** - Camera flash control
56
56
  - **Open URL** - External browser launch
57
57
  - **Vibration Pattern** - Custom vibration sequences
58
58
  - **App Review** - Google Play In-App Review prompt
59
59
  - **Screen Capture** - Take screenshots programmatically
60
- - **Health/Fitness** - Google Fit integration
60
+ - **Health/Fitness** - Health Connect integration
61
61
 
62
62
  ## Installation
63
63
 
@@ -83,7 +83,7 @@ Replace `app/src/main/assets/index.html` with your web app, or point to a dev se
83
83
  ```bash
84
84
  craft android build --html-path ../dist/index.html
85
85
  # or
86
- craft android build --dev-server http://192.168.1.100:3456
86
+ craft android build --dev-server http://10.0.2.2:3456
87
87
  ```
88
88
 
89
89
  ### 3. Open in Android Studio
@@ -120,27 +120,32 @@ Edit `craft.config.json` in your Android project:
120
120
  "enablePushNotifications": false,
121
121
  "enableSecureStorage": true,
122
122
  "enableGeolocation": true,
123
- "enableClipboard": true,
124
- "enableNetworkStatus": true,
125
- "enableAppReview": true,
126
- "enableFlashlight": true,
127
- "enableQRScanner": true,
128
- "enableFilePicker": true,
129
- "enableFileDownload": true,
130
- "enableSocialAuth": true,
131
- "enableAudioRecording": true,
132
- "enableVideoRecording": true,
133
- "enableMotionSensors": true,
134
- "enableLocalDatabase": true,
135
- "enableBluetooth": true,
136
- "enableNFC": true,
137
- "enableFitness": false,
138
- "enableScreenCapture": true,
139
- "minSdk": 24,
140
- "targetSdk": 34
123
+ "enableBackgroundLocation": false,
124
+ "enableHealthConnect": false,
125
+ "enableKeepAwake": true,
126
+ "enableDeepLinks": true,
127
+ "urlSchemes": ["myapp"],
128
+ "trustedOrigins": ["https://app.example.com"],
129
+ "minSdk": 26,
130
+ "compileSdk": 36,
131
+ "targetSdk": 35
141
132
  }
142
133
  ```
143
134
 
135
+ Only the fields shown above are generator options. Enabling background location
136
+ also enables foreground geolocation. Push notifications require a regular
137
+ `googleServicesFile` whose JSON contains a client matching the generated
138
+ Android package. Deep links require at least one valid URI scheme; scheme names
139
+ are normalized to lowercase.
140
+ Health Connect normalizes `minSdk` to at least 26 and `compileSdk` to at least 36.
141
+ A custom `appIconPath` must be a regular PNG, WebP, JPG, or GIF file; its supported
142
+ extension is preserved in the generated drawable resource.
143
+
144
+ Remote app and trusted-origin URLs must use HTTPS. Local development may use
145
+ HTTP with `localhost`, `127.0.0.1`, or the Android emulator host `10.0.2.2`.
146
+ Origins are normalized before they are stored, and a configured development
147
+ server is trusted automatically.
148
+
144
149
  ## JavaScript Bridge
145
150
 
146
151
  Once Craft is initialized, the `window.craft` object is available:
@@ -215,18 +220,19 @@ await window.craft.db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name
215
220
  await window.craft.db.execute('INSERT INTO users (name) VALUES (?)', ['Alice']);
216
221
  const users = await window.craft.db.query('SELECT * FROM users');
217
222
 
218
- // Bluetooth LE Scanning
219
- window.craft.startBluetoothScan();
223
+ // Bluetooth LE Scanning. Rejects when the device has no adapter, Bluetooth is
224
+ // switched off, permission is denied, or the platform scanner cannot start.
225
+ await window.craft.bluetooth.startScan();
220
226
  window.addEventListener('craftBluetoothDevice', (e) => {
221
227
  console.log(e.detail.name, e.detail.address, e.detail.rssi);
222
228
  });
223
- window.craft.stopBluetoothScan();
229
+ window.craft.bluetooth.stopScan();
224
230
 
225
231
  // NFC Tag Reading
226
232
  const nfcData = await window.craft.scanNFC();
227
233
  console.log(nfcData); // Tag content
228
234
 
229
- // Health/Fitness (Google Fit)
235
+ // Health/Fitness (Health Connect)
230
236
  await window.craft.requestFitnessAuthorization();
231
237
  const steps = await window.craft.getFitnessData('steps', startDate, endDate);
232
238
 
@@ -270,9 +276,10 @@ const text = await window.craft.clipboard.read();
270
276
  const device = window.craft.getDeviceInfo();
271
277
  console.log(device.model, device.manufacturer, device.systemVersion);
272
278
 
273
- // App Badge
274
- window.craft.setBadge(5);
275
- window.craft.clearBadge();
279
+ // App badges have no portable Android API, so this is always false.
280
+ if (window.craft.capabilities.appBadge) {
281
+ window.craft.setBadge(5);
282
+ }
276
283
 
277
284
  // Network Status
278
285
  const network = window.craft.getNetworkStatus();
@@ -369,15 +376,17 @@ window.craft.unlockOrientation(); // Allow all orientations
369
376
 
370
377
  // ==================== Medium Value Bridges ====================
371
378
 
372
- // Background Tasks (WorkManager)
373
- await window.craft.backgroundTask.register('sync-data');
374
- await window.craft.backgroundTask.schedule('sync-data', {
375
- delay: 900, // 15 minutes
376
- requiresNetwork: true,
377
- requiresCharging: false
378
- });
379
- await window.craft.backgroundTask.cancel('sync-data');
380
- await window.craft.backgroundTask.cancelAll();
379
+ // Background Tasks are not implemented on Android. These methods reject
380
+ // instead of reporting that work was scheduled when no worker can run.
381
+ try {
382
+ await window.craft.backgroundTask.schedule('sync-data', {
383
+ delay: 900,
384
+ requiresNetwork: true,
385
+ requiresCharging: false
386
+ });
387
+ } catch (error) {
388
+ console.error(error);
389
+ }
381
390
 
382
391
  // PDF Viewer (opens in external PDF app)
383
392
  await window.craft.openPDF('https://example.com/document.pdf');
@@ -453,16 +462,10 @@ await window.craft.widget.reload();
453
462
 
454
463
  // ==================== Google Assistant (App Actions) ====================
455
464
 
456
- // Note: App Actions are defined in shortcuts.xml, not dynamically
457
- // This stores action handlers for incoming intents
458
- await window.craft.siri.register('Open my app', 'open*app');
459
-
460
- // Remove a voice action
461
- await window.craft.siri.remove('open*app');
462
-
463
- // Listen for voice assistant invocations
464
- window.craft.siri.onInvoke((detail) => {
465
- console.log('Voice action:', detail.action);
465
+ // App Actions are declared statically in app/src/main/res/xml/shortcuts.xml.
466
+ // Listen for incoming Google Assistant intents forwarded by the bridge.
467
+ window.addEventListener('craftVoiceAction', (event) => {
468
+ console.log('Voice action:', event.detail.action);
466
469
  });
467
470
 
468
471
  // ==================== Wear OS Connectivity ====================
@@ -516,54 +519,58 @@ Options:
516
519
 
517
520
  - Android Studio with Android SDK
518
521
  - JDK 17+
519
- - Gradle 8.4+
522
+ - Gradle 8.11.1 (the generator creates a wrapper pinned to this version)
520
523
  - ADB (for device deployment)
521
524
 
522
525
  ## Permissions
523
526
 
524
- Add to your AndroidManifest.xml as needed:
525
-
526
- ```xml
527
- <!-- Speech Recognition -->
528
- <uses-permission android:name="android.permission.RECORD*AUDIO" />
529
-
530
- <!-- Camera -->
531
- <uses-permission android:name="android.permission.CAMERA" />
532
- <uses-permission android:name="android.permission.WRITE*EXTERNAL*STORAGE" />
527
+ The generator adds manifest permissions from enabled capabilities:
528
+
529
+ | Option | Android permissions |
530
+ | --- | --- |
531
+ | `enableSpeechRecognition` | `RECORD_AUDIO` |
532
+ | `enableCamera` | `CAMERA` |
533
+ | `enableHaptics` | `VIBRATE` |
534
+ | `enableBiometric` | `USE_BIOMETRIC` |
535
+ | `enableGeolocation` | `ACCESS_FINE_LOCATION`, `ACCESS_COARSE_LOCATION` |
536
+ | `enableBackgroundLocation` | foreground location plus `ACCESS_BACKGROUND_LOCATION` and foreground-service permissions |
537
+ | `enablePushNotifications` | `POST_NOTIFICATIONS` |
538
+ | `enableKeepAwake` | `WAKE_LOCK` |
539
+ | `enableHealthConnect` | the configured Health Connect read/write permissions |
540
+
541
+ Use `permissions.check('location')` and `permissions.request('location')` from
542
+ `craft-native/mobile` for runtime authorization. Android precise and
543
+ approximate access both count as a granted foreground location permission;
544
+ foreground fixes, watches, and recording accept either grant, with accuracy
545
+ controlled by Android. Obtaining a fix is still a separate operation. See the
546
+ [Permissions API](../../docs/api/mobile/permissions.md) for status semantics.
547
+
548
+ ## Promise Lifecycle and Concurrency
549
+
550
+ Promise-based bridge calls have one active request slot per native callback
551
+ channel. A second request on the same channel rejects immediately with an
552
+ `already in progress` error, without replacing the first request's callbacks.
553
+ Calls on different channels can run concurrently. Some related methods share a
554
+ channel—for example, shortcut mutations serialize with other shortcut
555
+ mutations—so callers should await one before starting the next.
556
+
557
+ Native success, error, cancellation, and timeout callbacks settle a request at
558
+ most once. Synchronous JavaScript-interface failures are converted to Promise
559
+ rejections. Permission requests use request IDs and a 30-second timeout, while
560
+ one-shot location requests use a 15-second timeout. Destroying the bridge
561
+ rejects all pending work and any new request, while callbacks queued by the old
562
+ Activity lifecycle are ignored. Reinjecting the bridge rejects old work and
563
+ opens a fresh runtime.
564
+
565
+ ## Template Validation
566
+
567
+ Android projects generated with the default and capability-heavy configurations
568
+ are assembled in CI, and their Kotlin permission tests are run. Contributors
569
+ with JDK 17, Android SDK 36, and Gradle 8.11.1 installed can run the same check:
533
570
 
534
- <!-- Location -->
535
- <uses-permission android:name="android.permission.ACCESS*FINE*LOCATION" />
536
- <uses-permission android:name="android.permission.ACCESS*COARSE*LOCATION" />
537
-
538
- <!-- Biometric -->
539
- <uses-permission android:name="android.permission.USE*BIOMETRIC" />
540
-
541
- <!-- Vibration -->
542
- <uses-permission android:name="android.permission.VIBRATE" />
543
-
544
- <!-- NFC -->
545
- <uses-permission android:name="android.permission.NFC" />
546
- <uses-feature android:name="android.hardware.nfc" android:required="false" />
547
-
548
- <!-- Bluetooth -->
549
- <uses-permission android:name="android.permission.BLUETOOTH" />
550
- <uses-permission android:name="android.permission.BLUETOOTH*ADMIN" />
551
- <uses-permission android:name="android.permission.BLUETOOTH*SCAN" />
552
- <uses-permission android:name="android.permission.BLUETOOTH*CONNECT" />
553
-
554
- <!-- Fitness (Google Fit) -->
555
- <uses-permission android:name="android.permission.ACTIVITY*RECOGNITION" />
556
-
557
- <!-- Contacts -->
558
- <uses-permission android:name="android.permission.READ*CONTACTS" />
559
- <uses-permission android:name="android.permission.WRITE*CONTACTS" />
560
-
561
- <!-- Calendar -->
562
- <uses-permission android:name="android.permission.READ*CALENDAR" />
563
- <uses-permission android:name="android.permission.WRITE*CALENDAR" />
564
-
565
- <!-- Notifications (Android 13+) -->
566
- <uses-permission android:name="android.permission.POST*NOTIFICATIONS" />
571
+ ```bash
572
+ cd packages/android
573
+ bun run test:templates
567
574
  ```
568
575
 
569
576
  ## Development Mode
@@ -574,14 +581,16 @@ For hot-reload during development:
574
581
  # Terminal 1: Start your dev server
575
582
  bun run dev # e.g., http://localhost:3456
576
583
 
577
- # Terminal 2: Build Android with dev server (use your Mac's IP)
578
- craft android build --dev-server http://192.168.1.100:3456
584
+ # Terminal 2: Build for an Android emulator
585
+ craft android build --dev-server http://10.0.2.2:3456
579
586
 
580
587
  # Open in Android Studio and run on device
581
588
  craft android open
582
589
  ```
583
590
 
584
- The app will load from your dev server instead of bundled HTML.
591
+ The emulator resolves `10.0.2.2` to the host machine. For a physical device,
592
+ use an HTTPS development endpoint. The app will load from that server instead
593
+ of bundled HTML.
585
594
 
586
595
  ## Publishing
587
596
 
@@ -597,6 +606,7 @@ This will:
597
606
  2. Output path for manual upload to Play Console
598
607
 
599
608
  For automated uploads, integrate with fastlane:
609
+
600
610
  ```bash
601
611
  fastlane supply --aab ./android/app/build/outputs/bundle/release/app-release.aab
602
612
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/android",
3
- "version": "0.0.89",
3
+ "version": "0.0.91",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/android#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,9 @@
19
19
  }
20
20
  },
21
21
  "scripts": {
22
- "build": "bun build.ts"
22
+ "build": "bun build.ts",
23
+ "test": "bun test",
24
+ "test:templates": "bun scripts/compile-templates.ts"
23
25
  },
24
26
  "files": [
25
27
  "dist",