craft-native 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/dist/android/src/index.js +285 -44
- package/dist/android/src/promise-runtime.d.ts +3 -0
- package/dist/android/templates/CraftBridge.kt.template +1951 -1193
- package/dist/android/templates/CraftHealthConnect.kt.template +45 -6
- package/dist/android/templates/CraftHealthConnectStub.kt.template +7 -1
- package/dist/android/templates/CraftNative.kt.template +2231 -0
- package/dist/android/templates/LocationRecordingService.kt.template +34 -9
- package/dist/android/templates/MainActivity.kt.template +25 -2
- package/dist/android/templates/proguard-rules.pro.template +4 -1
- package/dist/android/templates/test-bridges.html +10 -33
- package/dist/api/index.d.ts +1 -1
- package/dist/api/ios-advanced.d.ts +8 -5
- package/dist/api/live-activity-handle.d.ts +6 -0
- package/dist/api/mobile.d.ts +13 -5
- package/dist/api/window.d.ts +2 -0
- package/dist/cli.js +404 -128
- package/dist/index.cjs +65 -17
- package/dist/index.js +65 -17
- package/dist/ios/src/index.js +22 -4
- package/dist/ios/templates/CraftApp.swift +473 -60
- package/dist/ios/templates/CraftWatchApp.swift.template +8 -0
- package/dist/ios/templates/WatchApp.Info.plist.template +2 -0
- package/dist/ios/templates/WatchExtension.Info.plist.template +34 -0
- package/dist/ios/templates/project.yml.template +10 -4
- package/dist/mobile.js +36 -13
- package/dist/scaffold-version.d.ts +5 -0
- package/package.json +1 -1
- package/dist/android/templates/CraftBridgeExtensions.kt.template +0 -383
- package/dist/android/templates/CraftWidgetProvider.kt.template +0 -246
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package com.craft.runtime
|
|
2
2
|
|
|
3
3
|
import android.Manifest
|
|
4
4
|
import android.app.NotificationChannel
|
|
@@ -105,7 +105,7 @@ class LocationRecordingService : Service() {
|
|
|
105
105
|
val notification = NotificationCompat.Builder(this, CHANNEL)
|
|
106
106
|
.setSmallIcon(android.R.drawable.ic_menu_mylocation)
|
|
107
107
|
.setContentTitle("Recording your activity")
|
|
108
|
-
.setContentText("
|
|
108
|
+
.setContentText("${applicationInfo.loadLabel(packageManager)} is securely saving your route")
|
|
109
109
|
.setOngoing(true)
|
|
110
110
|
.setSilent(true)
|
|
111
111
|
.build()
|
|
@@ -119,24 +119,49 @@ class LocationRecordingService : Service() {
|
|
|
119
119
|
stopSelf()
|
|
120
120
|
return START_NOT_STICKY
|
|
121
121
|
}
|
|
122
|
-
else -> startUpdates()
|
|
122
|
+
else -> if (!startUpdates()) {
|
|
123
|
+
stopSelf()
|
|
124
|
+
return START_NOT_STICKY
|
|
125
|
+
}
|
|
123
126
|
}
|
|
124
|
-
return
|
|
127
|
+
return START_STICKY
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
private fun startUpdates() {
|
|
128
|
-
if (callback != null
|
|
129
|
-
if (
|
|
130
|
+
private fun startUpdates(): Boolean {
|
|
131
|
+
if (callback != null) return true
|
|
132
|
+
if (!CraftLocationRecordingStore.isActive(this)) return false
|
|
133
|
+
val hasForegroundLocation =
|
|
134
|
+
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
|
|
135
|
+
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
|
136
|
+
if (!hasForegroundLocation) {
|
|
137
|
+
CraftLocationRecordingStore.stop(this)
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
130
140
|
val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5_000)
|
|
131
141
|
.setMinUpdateIntervalMillis(2_000)
|
|
132
142
|
.setMaxUpdateDelayMillis(10_000)
|
|
133
143
|
.build()
|
|
134
|
-
|
|
144
|
+
val nextCallback = object : LocationCallback() {
|
|
135
145
|
override fun onLocationResult(result: LocationResult) {
|
|
136
146
|
for (location in result.locations) CraftLocationRecordingStore.append(this@LocationRecordingService, location)
|
|
137
147
|
}
|
|
138
148
|
}
|
|
139
|
-
|
|
149
|
+
callback = nextCallback
|
|
150
|
+
return try {
|
|
151
|
+
client.requestLocationUpdates(request, nextCallback, Looper.getMainLooper())
|
|
152
|
+
.addOnFailureListener {
|
|
153
|
+
if (callback === nextCallback) {
|
|
154
|
+
CraftLocationRecordingStore.stop(this)
|
|
155
|
+
stopUpdates()
|
|
156
|
+
stopSelf()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
true
|
|
160
|
+
} catch (error: Exception) {
|
|
161
|
+
callback = null
|
|
162
|
+
CraftLocationRecordingStore.stop(this)
|
|
163
|
+
false
|
|
164
|
+
}
|
|
140
165
|
}
|
|
141
166
|
|
|
142
167
|
private fun stopUpdates() {
|
|
@@ -3,6 +3,7 @@ package {{PACKAGE_NAME}}
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.os.Bundle
|
|
5
5
|
import android.content.Intent
|
|
6
|
+
import android.graphics.Bitmap
|
|
6
7
|
import android.net.Uri
|
|
7
8
|
import android.webkit.WebResourceRequest
|
|
8
9
|
import android.webkit.WebResourceError
|
|
@@ -44,6 +45,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
44
45
|
webView = findViewById(R.id.webview)
|
|
45
46
|
craftBridge = CraftBridge(this, webView, CraftHealthConnect(this, webView))
|
|
46
47
|
handleIncomingDeepLink(intent, dispatch = false)
|
|
48
|
+
if (savedInstanceState == null) handleIncomingShortcut(intent)
|
|
47
49
|
|
|
48
50
|
setupWebView()
|
|
49
51
|
loadContent()
|
|
@@ -67,6 +69,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
webView.webViewClient = object : WebViewClient() {
|
|
72
|
+
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
|
73
|
+
super.onPageStarted(view, url, favicon)
|
|
74
|
+
craftBridge.markBridgeLoading()
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
override fun onPageFinished(view: WebView?, url: String?) {
|
|
71
78
|
super.onPageFinished(view, url)
|
|
72
79
|
if (url != null && isTrustedUrl(url)) craftBridge.injectBridge()
|
|
@@ -149,6 +156,10 @@ class MainActivity : AppCompatActivity() {
|
|
|
149
156
|
if (dispatch) craftBridge.dispatchDeepLink(url)
|
|
150
157
|
}
|
|
151
158
|
|
|
159
|
+
private fun handleIncomingShortcut(source: Intent?) {
|
|
160
|
+
source?.getStringExtra(SHORTCUT_TYPE_EXTRA)?.let(craftBridge::dispatchShortcut)
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
private fun configJson(): JSONObject? {
|
|
153
164
|
return try {
|
|
154
165
|
assets.open("craft.config.json").bufferedReader().use { JSONObject(it.readText()) }
|
|
@@ -181,8 +192,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
181
192
|
|
|
182
193
|
private fun isTrustedUrl(value: String): Boolean {
|
|
183
194
|
val uri = Uri.parse(value)
|
|
184
|
-
if (uri.scheme == "https" && uri.host == BUNDLED_APP_HOST) return true
|
|
185
195
|
val origin = "${uri.scheme}://${uri.authority}"
|
|
196
|
+
if (origin == BUNDLED_APP_ORIGIN) return true
|
|
186
197
|
val localDevelopment = uri.scheme == "http" && uri.host in setOf("localhost", "127.0.0.1", "10.0.2.2")
|
|
187
198
|
return (uri.scheme == "https" || localDevelopment) && origin in trustedOrigins
|
|
188
199
|
}
|
|
@@ -191,6 +202,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
191
202
|
super.onNewIntent(intent)
|
|
192
203
|
setIntent(intent)
|
|
193
204
|
handleIncomingDeepLink(intent, dispatch = true)
|
|
205
|
+
handleIncomingShortcut(intent)
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
override fun onBackPressed() {
|
|
@@ -223,8 +235,19 @@ class MainActivity : AppCompatActivity() {
|
|
|
223
235
|
super.onActivityResult(requestCode, resultCode, data)
|
|
224
236
|
}
|
|
225
237
|
|
|
238
|
+
override fun onRequestPermissionsResult(
|
|
239
|
+
requestCode: Int,
|
|
240
|
+
permissions: Array<out String>,
|
|
241
|
+
grantResults: IntArray
|
|
242
|
+
) {
|
|
243
|
+
if (craftBridge.onRequestPermissionsResult(requestCode)) return
|
|
244
|
+
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
companion object {
|
|
227
248
|
private const val BUNDLED_APP_HOST = "appassets.androidplatform.net"
|
|
228
|
-
private const val
|
|
249
|
+
private const val BUNDLED_APP_ORIGIN = "https://appassets.androidplatform.net"
|
|
250
|
+
private const val BUNDLED_APP_URL = "$BUNDLED_APP_ORIGIN/"
|
|
251
|
+
private const val SHORTCUT_TYPE_EXTRA = "shortcut_type"
|
|
229
252
|
}
|
|
230
253
|
}
|
|
@@ -6,4 +6,7 @@
|
|
|
6
6
|
}
|
|
7
7
|
-keep class {{PACKAGE_NAME}}.CraftBridge { *; }
|
|
8
8
|
-keep class {{PACKAGE_NAME}}.CraftHealthConnect { *; }
|
|
9
|
-
|
|
9
|
+
# libcraft.so finds this exact class and registers its private external methods
|
|
10
|
+
# by their source names during JNI_OnLoad.
|
|
11
|
+
-keep class com.craft.runtime.CraftNative { *; }
|
|
12
|
+
-keep class com.craft.runtime.LocationRecordingService { *; }
|
|
@@ -307,9 +307,8 @@
|
|
|
307
307
|
|
|
308
308
|
<!-- Voice Assistant -->
|
|
309
309
|
<div class="section">
|
|
310
|
-
<h2>
|
|
311
|
-
<
|
|
312
|
-
<button class="btn" onclick="testRemoveSiri()">Remove Shortcut</button>
|
|
310
|
+
<h2>Google Assistant</h2>
|
|
311
|
+
<p>App Actions are declared statically in <code>shortcuts.xml</code>.</p>
|
|
313
312
|
<div id="siri-result" class="result">Results will appear here...</div>
|
|
314
313
|
</div>
|
|
315
314
|
|
|
@@ -766,6 +765,9 @@
|
|
|
766
765
|
|
|
767
766
|
function testBadge() {
|
|
768
767
|
if (!window.craft) return showResult('system-result', 'Craft not ready');
|
|
768
|
+
if (!window.craft.capabilities.appBadge) {
|
|
769
|
+
return showResult('system-result', 'App badges are unavailable on Android');
|
|
770
|
+
}
|
|
769
771
|
window.craft.setBadge(5);
|
|
770
772
|
setTimeout(() => window.craft.clearBadge(), 3000);
|
|
771
773
|
showResult('system-result', 'Badge set to 5 (will clear in 3s)');
|
|
@@ -1255,36 +1257,11 @@
|
|
|
1255
1257
|
}
|
|
1256
1258
|
}
|
|
1257
1259
|
|
|
1258
|
-
//
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
showResult('siri-result', 'Siri shortcut registered: ' + JSON.stringify(result));
|
|
1264
|
-
log('Siri shortcut registered');
|
|
1265
|
-
} catch (e) {
|
|
1266
|
-
showResult('siri-result', 'Error: ' + e.message);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
async function testRemoveSiri() {
|
|
1271
|
-
if (!window.craft?.siri) return showResult('siri-result', 'Siri API not available');
|
|
1272
|
-
try {
|
|
1273
|
-
const result = await window.craft.siri.remove('open_app');
|
|
1274
|
-
showResult('siri-result', 'Siri shortcut removed: ' + JSON.stringify(result));
|
|
1275
|
-
log('Siri shortcut removed');
|
|
1276
|
-
} catch (e) {
|
|
1277
|
-
showResult('siri-result', 'Error: ' + e.message);
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
// Listen for Siri invocations
|
|
1282
|
-
if (window.craft?.siri) {
|
|
1283
|
-
window.craft.siri.onInvoke((detail) => {
|
|
1284
|
-
log('Siri shortcut invoked: ' + JSON.stringify(detail));
|
|
1285
|
-
showResult('siri-result', 'Siri invoked: ' + JSON.stringify(detail));
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1260
|
+
// Listen for Google Assistant invocations declared in shortcuts.xml.
|
|
1261
|
+
window.addEventListener('craftVoiceAction', (event) => {
|
|
1262
|
+
log('Voice action invoked: ' + JSON.stringify(event.detail));
|
|
1263
|
+
showResult('siri-result', 'Voice action invoked: ' + JSON.stringify(event.detail));
|
|
1264
|
+
});
|
|
1288
1265
|
|
|
1289
1266
|
// Watch Connectivity tests
|
|
1290
1267
|
async function testWatchReachable() {
|
package/dist/api/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type { Platform, SystemInfo, ExecOptions, ExecResult, SpawnOptions } from
|
|
|
20
20
|
export { device, haptics, permissions, camera, biometrics, secureStorage, location, share, lifecycle, notifications, notifications as notification } from './mobile.js';
|
|
21
21
|
export type { DeviceInfo, DeviceCapabilities, HapticStyle, HapticNotificationType, PermissionType, PermissionStatus, CameraOptions as MobileCameraOptions, PhotoResult, BiometricType, Location, LocationOptions, ShareOptions, AppState, NotificationOptions as MobileNotificationOptions } from './mobile.js';
|
|
22
22
|
export { carplay, appClips, liveActivities, sharePlay, storeKit, appIntents, tipKit, focusFilters } from './ios-advanced.js';
|
|
23
|
-
export type { CarPlayTemplateType, CarPlayListItem, CarPlayGridItem, CarPlayTemplate, AppClipInvocation, LiveActivityContentState, LiveActivityAttributes, LiveActivityConfig, SharePlaySessionState, SharePlayParticipant, SharePlayActivity, ProductType, Product, Transaction, IntentParameterType, IntentParameter, AppIntent, TipDisplayFrequency, Tip, FocusStatus, FocusFilter } from './ios-advanced.js';
|
|
23
|
+
export type { CarPlayTemplateType, CarPlayListItem, CarPlayGridItem, CarPlayTemplate, AppClipInvocation, LiveActivityContentState, LiveActivityAttributes, LiveActivityConfig, LiveActivityHandle, SharePlaySessionState, SharePlayParticipant, SharePlayActivity, ProductType, Product, Transaction, IntentParameterType, IntentParameter, AppIntent, TipDisplayFrequency, Tip, FocusStatus, FocusFilter } from './ios-advanced.js';
|
|
24
24
|
export { materialYou, photoPicker, workManager, foregroundService, predictiveBack, appLanguage, widgets as androidWidgets, playBilling } from './android-advanced.js';
|
|
25
25
|
export type { MaterialYouColors, PhotoPickerMediaType, PhotoPickerResult, WorkConstraints, WorkRequest, WorkInfo, ForegroundServiceType, ForegroundNotification, BackEvent, WidgetSizeClass, WidgetConfig, WidgetData, PlayProduct, PlayPurchase } from './android-advanced.js';
|
|
26
26
|
export { touchBar, desktopWidgets, stageManager, handoff, sidecar, spotlight, quickActions, shareExtension, windowManagement } from './macos-advanced.js';
|
|
@@ -224,6 +224,11 @@ export interface LiveActivityConfig {
|
|
|
224
224
|
/** Relevance score (0-100) */
|
|
225
225
|
relevanceScore?: number;
|
|
226
226
|
}
|
|
227
|
+
export interface LiveActivityHandle {
|
|
228
|
+
readonly id: string;
|
|
229
|
+
update(state: LiveActivityContentState): Promise<void>;
|
|
230
|
+
end(finalState?: LiveActivityContentState): Promise<void>;
|
|
231
|
+
}
|
|
227
232
|
/**
|
|
228
233
|
* Live Activities API for Dynamic Island and Lock Screen.
|
|
229
234
|
*
|
|
@@ -236,14 +241,14 @@ export interface LiveActivityConfig {
|
|
|
236
241
|
* })
|
|
237
242
|
*
|
|
238
243
|
* // Update the activity
|
|
239
|
-
* await
|
|
244
|
+
* await activity.update({
|
|
240
245
|
* status: 'on-the-way',
|
|
241
246
|
* eta: '5 min',
|
|
242
247
|
* driverName: 'John'
|
|
243
248
|
* })
|
|
244
249
|
*
|
|
245
250
|
* // End the activity
|
|
246
|
-
* await
|
|
251
|
+
* await activity.end({ status: 'delivered' })
|
|
247
252
|
*/
|
|
248
253
|
export declare const liveActivities: {
|
|
249
254
|
/**
|
|
@@ -257,9 +262,7 @@ export declare const liveActivities: {
|
|
|
257
262
|
/**
|
|
258
263
|
* Start a new Live Activity.
|
|
259
264
|
*/
|
|
260
|
-
start(config: LiveActivityConfig): Promise<
|
|
261
|
-
id: string;
|
|
262
|
-
}>;
|
|
265
|
+
start(config: LiveActivityConfig): Promise<LiveActivityHandle>;
|
|
263
266
|
/**
|
|
264
267
|
* Update an existing Live Activity.
|
|
265
268
|
*/
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface TypedLiveActivityHandle<State> {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
update(state: State): Promise<void>;
|
|
4
|
+
end(finalState?: State): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare function createLiveActivityHandle<State>(id: string, update: (state: State) => Promise<void>, end: (finalState?: State) => Promise<void>): TypedLiveActivityHandle<State>;
|
package/dist/api/mobile.d.ts
CHANGED
|
@@ -729,13 +729,21 @@ export interface LiveActivityOptions extends LiveActivityState {
|
|
|
729
729
|
activityId: string;
|
|
730
730
|
title: string;
|
|
731
731
|
}
|
|
732
|
-
export
|
|
733
|
-
|
|
734
|
-
id: string;
|
|
735
|
-
}>;
|
|
732
|
+
export interface LiveActivityHandle {
|
|
733
|
+
readonly id: string;
|
|
736
734
|
update(state: LiveActivityState): Promise<void>;
|
|
735
|
+
end(finalState?: LiveActivityState): Promise<void>;
|
|
736
|
+
}
|
|
737
|
+
export interface LiveActivitiesApi {
|
|
738
|
+
start(options: LiveActivityOptions): Promise<LiveActivityHandle>;
|
|
739
|
+
update(id: string, state: LiveActivityState): Promise<void>;
|
|
740
|
+
/** @deprecated Pass the activity id returned by start, or use handle.update(state). */
|
|
741
|
+
update(state: LiveActivityState): Promise<void>;
|
|
742
|
+
end(id: string, finalState?: LiveActivityState): Promise<void>;
|
|
743
|
+
/** @deprecated Pass the activity id returned by start, or use handle.end(finalState). */
|
|
737
744
|
end(): Promise<void>;
|
|
738
|
-
}
|
|
745
|
+
}
|
|
746
|
+
export declare const liveActivities: LiveActivitiesApi;
|
|
739
747
|
export declare const watchConnectivity: {
|
|
740
748
|
send(message: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
741
749
|
updateContext(context: Record<string, unknown>): Promise<void>;
|
package/dist/api/window.d.ts
CHANGED
|
@@ -198,6 +198,8 @@ export declare class Window {
|
|
|
198
198
|
get isClosed(): boolean;
|
|
199
199
|
private _setupEventListeners;
|
|
200
200
|
private _cleanupEventListeners;
|
|
201
|
+
/** Restore a retained handle after native code has shown it again. */
|
|
202
|
+
private _markOpen;
|
|
201
203
|
private _emit;
|
|
202
204
|
/**
|
|
203
205
|
* Register an event handler
|