@craft-native/android 0.0.72
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 +606 -0
- package/dist/index.js +410 -0
- package/dist/index.js.map +10 -0
- package/package.json +38 -0
- package/templates/AndroidManifest.xml.template +43 -0
- package/templates/CraftBridge.kt.template +3880 -0
- package/templates/CraftBridgeExtensions.kt.template +383 -0
- package/templates/CraftHealthConnect.kt.template +200 -0
- package/templates/CraftHealthConnectStub.kt.template +28 -0
- package/templates/CraftWidgetProvider.kt.template +246 -0
- package/templates/LocationRecordingService.kt.template +160 -0
- package/templates/MainActivity.kt.template +230 -0
- package/templates/build.gradle.kts.app.template +102 -0
- package/templates/build.gradle.kts.project.template +6 -0
- package/templates/fastlane/Appfile +25 -0
- package/templates/fastlane/Fastfile +192 -0
- package/templates/fastlane/Gemfile +7 -0
- package/templates/github-workflow-android.yml +248 -0
- package/templates/github-workflow-test.yml +329 -0
- package/templates/proguard-rules.pro.template +9 -0
- package/templates/settings.gradle.kts.template +18 -0
- package/templates/test-bridges.html +1463 -0
package/README.md
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
# @craft-native/android
|
|
2
|
+
|
|
3
|
+
Build native Android apps with web technologies using Craft.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Core
|
|
8
|
+
|
|
9
|
+
- **WebView** - Native Android WebView with full JavaScript support
|
|
10
|
+
- **Dark Mode** - Native dark/light theme support
|
|
11
|
+
|
|
12
|
+
### Input & Feedback
|
|
13
|
+
|
|
14
|
+
- **Native Speech Recognition** - Android SpeechRecognizer
|
|
15
|
+
- **Haptic Feedback** - Vibration patterns for different feedback types
|
|
16
|
+
- **Audio Recording** - MediaRecorder with compressed output
|
|
17
|
+
- **Video Recording** - Camera video capture
|
|
18
|
+
|
|
19
|
+
### Device Access
|
|
20
|
+
|
|
21
|
+
- **Camera & Gallery** - Take photos or pick from gallery
|
|
22
|
+
- **Barcode/QR Scanner** - ML Kit barcode scanning
|
|
23
|
+
- **File Picker** - Document picker for any file type
|
|
24
|
+
- **File Download** - DownloadManager integration
|
|
25
|
+
|
|
26
|
+
### Sensors & Location
|
|
27
|
+
|
|
28
|
+
- **Geolocation** - GPS and network location via FusedLocationProvider
|
|
29
|
+
- **Motion Sensors** - Accelerometer & gyroscope via SensorManager
|
|
30
|
+
- **NFC** - Read NFC tags via NfcAdapter
|
|
31
|
+
|
|
32
|
+
### Communication
|
|
33
|
+
|
|
34
|
+
- **Share Sheet** - Native Android sharing
|
|
35
|
+
- **Clipboard** - Read and write to system clipboard
|
|
36
|
+
- **Push Notifications** - Firebase Cloud Messaging ready
|
|
37
|
+
|
|
38
|
+
### Security & Auth
|
|
39
|
+
|
|
40
|
+
- **Biometric Auth** - Fingerprint / Face unlock
|
|
41
|
+
- **Social Auth** - Google Sign In
|
|
42
|
+
- **Secure Storage** - EncryptedSharedPreferences
|
|
43
|
+
|
|
44
|
+
### Data & Storage
|
|
45
|
+
|
|
46
|
+
- **Local Database** - SQLite with full SQL support
|
|
47
|
+
- **Bluetooth LE** - BluetoothLeScanner
|
|
48
|
+
|
|
49
|
+
### System
|
|
50
|
+
|
|
51
|
+
- **Device Info** - Device model, OS version, screen info
|
|
52
|
+
- **Network Status** - Connection type monitoring (WiFi/Cellular)
|
|
53
|
+
- **App Badge** - Notification badge count
|
|
54
|
+
- **App State** - Foreground/background detection
|
|
55
|
+
- **Flashlight** - Camera flash control
|
|
56
|
+
- **Open URL** - External browser launch
|
|
57
|
+
- **Vibration Pattern** - Custom vibration sequences
|
|
58
|
+
- **App Review** - Google Play In-App Review prompt
|
|
59
|
+
- **Screen Capture** - Take screenshots programmatically
|
|
60
|
+
- **Health/Fitness** - Google Fit integration
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
The Android support is built into the main `craft` CLI:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bun add craft-native
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
### 1. Initialize Project
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
craft android init MyApp --package com.example.myapp
|
|
76
|
+
cd android
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. Add Your Web Content
|
|
80
|
+
|
|
81
|
+
Replace `app/src/main/assets/index.html` with your web app, or point to a dev server:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
craft android build --html-path ../dist/index.html
|
|
85
|
+
# or
|
|
86
|
+
craft android build --dev-server http://192.168.1.100:3456
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Open in Android Studio
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
craft android open
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Run on Device
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
craft android run
|
|
99
|
+
# or specify device
|
|
100
|
+
craft android run --device emulator-5554
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
Edit `craft.config.json` in your Android project:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"appName": "MyApp",
|
|
110
|
+
"packageName": "com.example.myapp",
|
|
111
|
+
"version": "1.0.0",
|
|
112
|
+
"versionCode": 1,
|
|
113
|
+
"darkMode": true,
|
|
114
|
+
"backgroundColor": "#1a1a2e",
|
|
115
|
+
"enableSpeechRecognition": true,
|
|
116
|
+
"enableHaptics": true,
|
|
117
|
+
"enableShare": true,
|
|
118
|
+
"enableCamera": true,
|
|
119
|
+
"enableBiometric": true,
|
|
120
|
+
"enablePushNotifications": false,
|
|
121
|
+
"enableSecureStorage": true,
|
|
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
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## JavaScript Bridge
|
|
145
|
+
|
|
146
|
+
Once Craft is initialized, the `window.craft` object is available:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
// Wait for Craft to be ready
|
|
150
|
+
window.addEventListener('craftReady', (e) => {
|
|
151
|
+
console.log('Platform:', e.detail.platform); // 'android'
|
|
152
|
+
console.log('Capabilities:', e.detail.capabilities);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Haptic feedback
|
|
156
|
+
window.craft.haptic('light'); // light, medium, heavy
|
|
157
|
+
window.craft.haptic('success'); // success, warning, error
|
|
158
|
+
window.craft.haptic('selection');
|
|
159
|
+
|
|
160
|
+
// Speech recognition
|
|
161
|
+
window.craft.startListening();
|
|
162
|
+
window.craft.stopListening();
|
|
163
|
+
|
|
164
|
+
// Listen for speech events
|
|
165
|
+
window.addEventListener('craftSpeechStart', () => { /* recording started */ });
|
|
166
|
+
window.addEventListener('craftSpeechResult', (e) => {
|
|
167
|
+
console.log(e.detail.transcript); // "hello world"
|
|
168
|
+
console.log(e.detail.isFinal); // true/false
|
|
169
|
+
});
|
|
170
|
+
window.addEventListener('craftSpeechEnd', () => { /* recording stopped */ });
|
|
171
|
+
window.addEventListener('craftSpeechError', (e) => {
|
|
172
|
+
console.error(e.detail.error);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Share
|
|
176
|
+
window.craft.share('Check out this app!', 'My App');
|
|
177
|
+
|
|
178
|
+
// Camera & Gallery
|
|
179
|
+
const imageBase64 = await window.craft.openCamera();
|
|
180
|
+
const selectedImage = await window.craft.pickImage();
|
|
181
|
+
|
|
182
|
+
// Barcode/QR Scanner (ML Kit)
|
|
183
|
+
const scannedCode = await window.craft.scanQRCode();
|
|
184
|
+
console.log(scannedCode); // "https://example.com"
|
|
185
|
+
|
|
186
|
+
// File Picker
|
|
187
|
+
const file = await window.craft.pickFile(['image/*', 'application/pdf']);
|
|
188
|
+
console.log(file.name, file.data); // base64 encoded
|
|
189
|
+
|
|
190
|
+
// File Download
|
|
191
|
+
await window.craft.downloadFile('https://example.com/file.pdf', 'document.pdf');
|
|
192
|
+
await window.craft.saveFile(base64Data, 'image.png', 'image/png');
|
|
193
|
+
|
|
194
|
+
// Social Auth (Google Sign In)
|
|
195
|
+
const user = await window.craft.signInWithGoogle();
|
|
196
|
+
console.log(user.userId, user.email, user.displayName, user.idToken);
|
|
197
|
+
|
|
198
|
+
// Audio Recording
|
|
199
|
+
await window.craft.startAudioRecording();
|
|
200
|
+
const audioBase64 = await window.craft.stopAudioRecording();
|
|
201
|
+
|
|
202
|
+
// Video Recording
|
|
203
|
+
const videoBase64 = await window.craft.startVideoRecording();
|
|
204
|
+
|
|
205
|
+
// Motion Sensors
|
|
206
|
+
window.craft.startMotionUpdates();
|
|
207
|
+
window.addEventListener('craftMotionUpdate', (e) => {
|
|
208
|
+
console.log('Accelerometer:', e.detail.accelerometer); // {x, y, z}
|
|
209
|
+
console.log('Gyroscope:', e.detail.gyroscope); // {x, y, z}
|
|
210
|
+
});
|
|
211
|
+
window.craft.stopMotionUpdates();
|
|
212
|
+
|
|
213
|
+
// Local Database (SQLite)
|
|
214
|
+
await window.craft.db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
|
|
215
|
+
await window.craft.db.execute('INSERT INTO users (name) VALUES (?)', ['Alice']);
|
|
216
|
+
const users = await window.craft.db.query('SELECT * FROM users');
|
|
217
|
+
|
|
218
|
+
// Bluetooth LE Scanning
|
|
219
|
+
window.craft.startBluetoothScan();
|
|
220
|
+
window.addEventListener('craftBluetoothDevice', (e) => {
|
|
221
|
+
console.log(e.detail.name, e.detail.address, e.detail.rssi);
|
|
222
|
+
});
|
|
223
|
+
window.craft.stopBluetoothScan();
|
|
224
|
+
|
|
225
|
+
// NFC Tag Reading
|
|
226
|
+
const nfcData = await window.craft.scanNFC();
|
|
227
|
+
console.log(nfcData); // Tag content
|
|
228
|
+
|
|
229
|
+
// Health/Fitness (Google Fit)
|
|
230
|
+
await window.craft.requestFitnessAuthorization();
|
|
231
|
+
const steps = await window.craft.getFitnessData('steps', startDate, endDate);
|
|
232
|
+
|
|
233
|
+
// Screen Capture
|
|
234
|
+
const screenshotBase64 = await window.craft.takeScreenshot();
|
|
235
|
+
|
|
236
|
+
// Biometric authentication
|
|
237
|
+
try {
|
|
238
|
+
const authenticated = await window.craft.authenticate('Confirm your identity');
|
|
239
|
+
if (authenticated) {
|
|
240
|
+
console.log('User authenticated!');
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.log('Authentication failed:', error.message);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Push notifications
|
|
247
|
+
const token = await window.craft.registerPush();
|
|
248
|
+
console.log('Push token:', token);
|
|
249
|
+
|
|
250
|
+
// Secure storage (encrypted)
|
|
251
|
+
await window.craft.secureStore.set('api*key', 'secret123');
|
|
252
|
+
const apiKey = await window.craft.secureStore.get('api*key');
|
|
253
|
+
await window.craft.secureStore.remove('api*key');
|
|
254
|
+
|
|
255
|
+
// Geolocation
|
|
256
|
+
const position = await window.craft.getCurrentPosition();
|
|
257
|
+
console.log(position.latitude, position.longitude);
|
|
258
|
+
|
|
259
|
+
// Watch position (continuous updates)
|
|
260
|
+
const watchId = window.craft.watchPosition((position) => {
|
|
261
|
+
console.log('New position:', position);
|
|
262
|
+
});
|
|
263
|
+
window.craft.clearWatch(watchId);
|
|
264
|
+
|
|
265
|
+
// Clipboard
|
|
266
|
+
await window.craft.clipboard.write('Hello World');
|
|
267
|
+
const text = await window.craft.clipboard.read();
|
|
268
|
+
|
|
269
|
+
// Device Info
|
|
270
|
+
const device = window.craft.getDeviceInfo();
|
|
271
|
+
console.log(device.model, device.manufacturer, device.systemVersion);
|
|
272
|
+
|
|
273
|
+
// App Badge
|
|
274
|
+
window.craft.setBadge(5);
|
|
275
|
+
window.craft.clearBadge();
|
|
276
|
+
|
|
277
|
+
// Network Status
|
|
278
|
+
const network = window.craft.getNetworkStatus();
|
|
279
|
+
console.log(network.isConnected, network.type); // true, 'wifi'
|
|
280
|
+
|
|
281
|
+
// Network change listener
|
|
282
|
+
window.craft.onNetworkChange((status) => {
|
|
283
|
+
console.log('Network changed:', status);
|
|
284
|
+
});
|
|
285
|
+
window.craft.offNetworkChange();
|
|
286
|
+
|
|
287
|
+
// App Review (Google Play In-App Review)
|
|
288
|
+
await window.craft.requestReview();
|
|
289
|
+
|
|
290
|
+
// Flashlight
|
|
291
|
+
window.craft.setFlashlight(true); // turn on
|
|
292
|
+
window.craft.setFlashlight(false); // turn off
|
|
293
|
+
window.craft.toggleFlashlight(); // toggle
|
|
294
|
+
|
|
295
|
+
// Open URL (external browser)
|
|
296
|
+
window.craft.openURL('https://example.com');
|
|
297
|
+
|
|
298
|
+
// Vibration Pattern (custom durations in ms)
|
|
299
|
+
window.craft.vibrate([100, 50, 100, 50, 200]); // [on, off, on, off, on]
|
|
300
|
+
|
|
301
|
+
// App State
|
|
302
|
+
const state = window.craft.getAppState(); // 'active', 'inactive', 'background'
|
|
303
|
+
|
|
304
|
+
// App state change listener
|
|
305
|
+
window.craft.onAppStateChange((state) => {
|
|
306
|
+
console.log('App state:', state);
|
|
307
|
+
});
|
|
308
|
+
window.craft.offAppStateChange();
|
|
309
|
+
|
|
310
|
+
// Logging (appears in Logcat)
|
|
311
|
+
window.craft.log('Debug message');
|
|
312
|
+
|
|
313
|
+
// ==================== High Value Bridges ====================
|
|
314
|
+
|
|
315
|
+
// Contacts
|
|
316
|
+
const contacts = await window.craft.getContacts();
|
|
317
|
+
console.log(contacts); // [{id, displayName, phoneNumbers, emailAddresses}]
|
|
318
|
+
|
|
319
|
+
const newContactId = await window.craft.addContact({
|
|
320
|
+
displayName: 'John Doe',
|
|
321
|
+
phone: '+1234567890',
|
|
322
|
+
email: 'john@example.com'
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Calendar Events
|
|
326
|
+
const events = await window.craft.getCalendarEvents(startDateMs, endDateMs);
|
|
327
|
+
console.log(events); // [{id, title, description, startDate, endDate, location, isAllDay}]
|
|
328
|
+
|
|
329
|
+
const newEventId = await window.craft.createCalendarEvent({
|
|
330
|
+
title: 'Meeting',
|
|
331
|
+
description: 'Discuss project',
|
|
332
|
+
location: 'Office',
|
|
333
|
+
startDate: Date.now(),
|
|
334
|
+
endDate: Date.now() + 3600000,
|
|
335
|
+
isAllDay: false
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await window.craft.deleteCalendarEvent(eventId);
|
|
339
|
+
|
|
340
|
+
// Local Notifications
|
|
341
|
+
const notificationId = await window.craft.scheduleNotification({
|
|
342
|
+
id: 'reminder-1',
|
|
343
|
+
title: 'Reminder',
|
|
344
|
+
body: 'Don\'t forget!',
|
|
345
|
+
timestamp: Date.now() + 60000, // 1 minute from now
|
|
346
|
+
// or use delay: 60000 // delay in ms
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await window.craft.cancelNotification('reminder-1');
|
|
350
|
+
await window.craft.cancelAllNotifications();
|
|
351
|
+
|
|
352
|
+
// In-App Purchase (Google Play Billing)
|
|
353
|
+
const products = await window.craft.getProducts(['product*id*1', 'product*id*2']);
|
|
354
|
+
console.log(products); // [{productId, title, description, price}]
|
|
355
|
+
|
|
356
|
+
const purchaseResult = await window.craft.purchase('product*id*1');
|
|
357
|
+
console.log(purchaseResult); // {purchaseToken, productId, ...}
|
|
358
|
+
|
|
359
|
+
await window.craft.restorePurchases();
|
|
360
|
+
|
|
361
|
+
// Keep Screen Awake
|
|
362
|
+
window.craft.setKeepAwake(true); // Prevent screen dimming
|
|
363
|
+
window.craft.setKeepAwake(false); // Allow screen dimming
|
|
364
|
+
|
|
365
|
+
// Orientation Lock
|
|
366
|
+
window.craft.lockOrientation('portrait'); // Lock to portrait
|
|
367
|
+
window.craft.lockOrientation('landscape'); // Lock to landscape
|
|
368
|
+
window.craft.unlockOrientation(); // Allow all orientations
|
|
369
|
+
|
|
370
|
+
// ==================== Medium Value Bridges ====================
|
|
371
|
+
|
|
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();
|
|
381
|
+
|
|
382
|
+
// PDF Viewer (opens in external PDF app)
|
|
383
|
+
await window.craft.openPDF('https://example.com/document.pdf');
|
|
384
|
+
await window.craft.openPDF(base64PdfData, 5); // Open at page 5
|
|
385
|
+
await window.craft.closePDF();
|
|
386
|
+
|
|
387
|
+
// Contacts Picker (shows native picker UI)
|
|
388
|
+
const contact = await window.craft.pickContact();
|
|
389
|
+
console.log(contact); // {id, displayName, phoneNumbers, emailAddresses}
|
|
390
|
+
|
|
391
|
+
const contacts = await window.craft.pickContact({multiple: true}); // Select multiple
|
|
392
|
+
|
|
393
|
+
// App Shortcuts (Android 7.1+, long press)
|
|
394
|
+
await window.craft.shortcuts.set([
|
|
395
|
+
{type: 'new-message', title: 'New Message', subtitle: 'Start composing'},
|
|
396
|
+
{type: 'search', title: 'Search'}
|
|
397
|
+
]);
|
|
398
|
+
window.craft.shortcuts.onShortcut((shortcut) => {
|
|
399
|
+
console.log('Shortcut activated:', shortcut.type);
|
|
400
|
+
});
|
|
401
|
+
await window.craft.shortcuts.clear();
|
|
402
|
+
|
|
403
|
+
// Shared Preferences (cross-app data with named groups)
|
|
404
|
+
await window.craft.sharedKeychain.set('user*token', 'abc123', 'mygroup'); // group optional
|
|
405
|
+
const result = await window.craft.sharedKeychain.get('user*token', 'mygroup');
|
|
406
|
+
console.log(result.value); // 'abc123'
|
|
407
|
+
await window.craft.sharedKeychain.remove('user*token');
|
|
408
|
+
|
|
409
|
+
// Local Auth Persistence (skip re-auth for a duration)
|
|
410
|
+
await window.craft.authPersistence.enable(300); // 5 minutes
|
|
411
|
+
const status = await window.craft.authPersistence.check();
|
|
412
|
+
if (status.isValid) {
|
|
413
|
+
console.log('Session valid for', status.remainingSeconds, 'more seconds');
|
|
414
|
+
}
|
|
415
|
+
await window.craft.authPersistence.clear();
|
|
416
|
+
|
|
417
|
+
// ==================== Nice to Have Bridges ====================
|
|
418
|
+
|
|
419
|
+
// AR (ARCore) - Note: Full ARCore requires native Activity integration
|
|
420
|
+
// Use Sceneform or AR Fragment for complete AR functionality
|
|
421
|
+
// The bridge API is consistent with iOS for cross-platform code:
|
|
422
|
+
await window.craft.ar.start({planeDetection: true});
|
|
423
|
+
// Returns error on Android: "ARCore requires native Activity integration"
|
|
424
|
+
|
|
425
|
+
// ML (ML Kit) - Full support for ML operations
|
|
426
|
+
// First capture an image
|
|
427
|
+
const image = await window.craft.openCamera();
|
|
428
|
+
|
|
429
|
+
// Image Classification (Labeling) - Identify what's in the image
|
|
430
|
+
const labels = await window.craft.ml.classifyImage(image);
|
|
431
|
+
console.log(labels); // [{label: 'Food', confidence: 0.95, index: 1}, ...]
|
|
432
|
+
|
|
433
|
+
// Object Detection - Detect and locate objects
|
|
434
|
+
const objects = await window.craft.ml.detectObjects(image);
|
|
435
|
+
console.log(objects); // [{labels: [...], boundingBox: {x, y, width, height}, trackingId: 1}]
|
|
436
|
+
|
|
437
|
+
// Text Recognition (OCR) - Extract text from image
|
|
438
|
+
const textResults = await window.craft.ml.recognizeText(image);
|
|
439
|
+
console.log(textResults); // [{text: 'Hello World', confidence: 0.98, boundingBox: {...}}]
|
|
440
|
+
|
|
441
|
+
// ==================== Widgets (AppWidgetProvider) ====================
|
|
442
|
+
|
|
443
|
+
// Update widget data - displayed on home screen widget
|
|
444
|
+
await window.craft.widget.update({
|
|
445
|
+
title: 'My App',
|
|
446
|
+
subtitle: 'Latest update',
|
|
447
|
+
value: '42',
|
|
448
|
+
icon: 'ic*star' // Android drawable name
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Reload all widgets
|
|
452
|
+
await window.craft.widget.reload();
|
|
453
|
+
|
|
454
|
+
// ==================== Google Assistant (App Actions) ====================
|
|
455
|
+
|
|
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);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// ==================== Wear OS Connectivity ====================
|
|
469
|
+
|
|
470
|
+
// Note: Full Wear OS support requires a companion watch app
|
|
471
|
+
// This provides the JavaScript API for future integration
|
|
472
|
+
|
|
473
|
+
// Check if watch is reachable
|
|
474
|
+
const status = await window.craft.watch.isReachable();
|
|
475
|
+
console.log(status.reachable); // true/false
|
|
476
|
+
|
|
477
|
+
// Send message to watch (requires companion app)
|
|
478
|
+
const reply = await window.craft.watch.send({
|
|
479
|
+
action: 'ping',
|
|
480
|
+
data: { timestamp: Date.now() }
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Update application context
|
|
484
|
+
await window.craft.watch.updateContext({
|
|
485
|
+
lastUpdate: Date.now(),
|
|
486
|
+
status: 'active'
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Listen for messages from watch
|
|
490
|
+
window.craft.watch.onMessage((message) => {
|
|
491
|
+
console.log('Watch message:', message);
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## CLI Reference
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
craft android init <name> # Initialize new Android project
|
|
499
|
+
craft android build # Build APK
|
|
500
|
+
craft android build --release # Build release APK
|
|
501
|
+
craft android build --watch # Watch for changes and rebuild
|
|
502
|
+
craft android open # Open in Android Studio
|
|
503
|
+
craft android run # Run on connected device/emulator
|
|
504
|
+
|
|
505
|
+
Options:
|
|
506
|
+
--package <name> Package name (e.g., com.example.app)
|
|
507
|
+
--html-path <path> Path to HTML file
|
|
508
|
+
-d, --dev-server <url> Development server URL
|
|
509
|
+
-o, --output <dir> Output directory (default: ./android)
|
|
510
|
+
--release Build release APK
|
|
511
|
+
-w, --watch Watch mode
|
|
512
|
+
-d, --device <id> Target device ID
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
## Requirements
|
|
516
|
+
|
|
517
|
+
- Android Studio with Android SDK
|
|
518
|
+
- JDK 17+
|
|
519
|
+
- Gradle 8.4+
|
|
520
|
+
- ADB (for device deployment)
|
|
521
|
+
|
|
522
|
+
## Permissions
|
|
523
|
+
|
|
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" />
|
|
533
|
+
|
|
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" />
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Development Mode
|
|
570
|
+
|
|
571
|
+
For hot-reload during development:
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
# Terminal 1: Start your dev server
|
|
575
|
+
bun run dev # e.g., http://localhost:3456
|
|
576
|
+
|
|
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
|
|
579
|
+
|
|
580
|
+
# Open in Android Studio and run on device
|
|
581
|
+
craft android open
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
The app will load from your dev server instead of bundled HTML.
|
|
585
|
+
|
|
586
|
+
## Publishing
|
|
587
|
+
|
|
588
|
+
Build release APK/AAB:
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
craft publish --android
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
This will:
|
|
595
|
+
|
|
596
|
+
1. Build a release AAB (Android App Bundle)
|
|
597
|
+
2. Output path for manual upload to Play Console
|
|
598
|
+
|
|
599
|
+
For automated uploads, integrate with fastlane:
|
|
600
|
+
```bash
|
|
601
|
+
fastlane supply --aab ./android/app/build/outputs/bundle/release/app-release.aab
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
## License
|
|
605
|
+
|
|
606
|
+
MIT
|