craft-native 0.0.90 → 0.0.92

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.
Files changed (31) hide show
  1. package/dist/android/src/index.d.ts +36 -1
  2. package/dist/android/src/index.js +332 -44
  3. package/dist/android/src/promise-runtime.d.ts +3 -0
  4. package/dist/android/templates/CraftBridge.kt.template +2167 -1177
  5. package/dist/android/templates/CraftHealthConnect.kt.template +45 -6
  6. package/dist/android/templates/CraftHealthConnectStub.kt.template +7 -1
  7. package/dist/android/templates/CraftNative.kt.template +2250 -0
  8. package/dist/android/templates/LocationRecordingService.kt.template +34 -9
  9. package/dist/android/templates/MainActivity.kt.template +42 -8
  10. package/dist/android/templates/proguard-rules.pro.template +4 -1
  11. package/dist/android/templates/test-bridges.html +10 -33
  12. package/dist/api/index.d.ts +1 -1
  13. package/dist/api/ios-advanced.d.ts +8 -5
  14. package/dist/api/live-activity-handle.d.ts +6 -0
  15. package/dist/api/mobile.d.ts +25 -7
  16. package/dist/api/window.d.ts +2 -0
  17. package/dist/cli.js +452 -129
  18. package/dist/index.cjs +77 -22
  19. package/dist/index.js +77 -22
  20. package/dist/ios/src/index.d.ts +1 -1
  21. package/dist/ios/src/index.js +23 -5
  22. package/dist/ios/templates/CraftApp.swift +706 -92
  23. package/dist/ios/templates/CraftWatchApp.swift.template +8 -0
  24. package/dist/ios/templates/WatchApp.Info.plist.template +2 -0
  25. package/dist/ios/templates/WatchExtension.Info.plist.template +34 -0
  26. package/dist/ios/templates/project.yml.template +10 -4
  27. package/dist/mobile.js +52 -21
  28. package/dist/scaffold-version.d.ts +5 -0
  29. package/package.json +1 -1
  30. package/dist/android/templates/CraftBridgeExtensions.kt.template +0 -383
  31. package/dist/android/templates/CraftWidgetProvider.kt.template +0 -246
@@ -102,6 +102,14 @@ extension CraftWatchSession: WCSessionDelegate {
102
102
  nonisolated func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
103
103
  Task { @MainActor in apply(userInfo) }
104
104
  }
105
+
106
+ #if os(iOS)
107
+ nonisolated func sessionDidBecomeInactive(_ session: WCSession) {}
108
+
109
+ nonisolated func sessionDidDeactivate(_ session: WCSession) {
110
+ session.activate()
111
+ }
112
+ #endif
105
113
  }
106
114
 
107
115
  struct CraftWatchRecordingView: View {
@@ -22,5 +22,7 @@
22
22
  <string>$(CURRENT_PROJECT_VERSION)</string>
23
23
  <key>WKCompanionAppBundleIdentifier</key>
24
24
  <string>{{BUNDLE_ID}}</string>
25
+ <key>WKWatchKitApp</key>
26
+ <true/>
25
27
  </dict>
26
28
  </plist>
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
7
+ <key>CFBundleDisplayName</key>
8
+ <string>{{APP_NAME}}</string>
9
+ <key>CFBundleExecutable</key>
10
+ <string>$(EXECUTABLE_NAME)</string>
11
+ <key>CFBundleIdentifier</key>
12
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13
+ <key>CFBundleInfoDictionaryVersion</key>
14
+ <string>6.0</string>
15
+ <key>CFBundleName</key>
16
+ <string>$(PRODUCT_NAME)</string>
17
+ <key>CFBundlePackageType</key>
18
+ <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
19
+ <key>CFBundleShortVersionString</key>
20
+ <string>$(MARKETING_VERSION)</string>
21
+ <key>CFBundleVersion</key>
22
+ <string>$(CURRENT_PROJECT_VERSION)</string>
23
+ <key>NSExtension</key>
24
+ <dict>
25
+ <key>NSExtensionAttributes</key>
26
+ <dict>
27
+ <key>WKAppBundleIdentifier</key>
28
+ <string>{{BUNDLE_ID}}.watchkitapp</string>
29
+ </dict>
30
+ <key>NSExtensionPointIdentifier</key>
31
+ <string>com.apple.watchkit</string>
32
+ </dict>
33
+ </dict>
34
+ </plist>
@@ -4,10 +4,16 @@ options:
4
4
  deploymentTarget:
5
5
  iOS: "{{IOS_VERSION}}"
6
6
  settings:
7
- MARKETING_VERSION: "{{VERSION}}"
8
- CURRENT_PROJECT_VERSION: "{{BUILD_NUMBER}}"
9
- CODE_SIGN_STYLE: Automatic
10
- DEVELOPMENT_TEAM: "{{TEAM_ID}}"
7
+ base:
8
+ MARKETING_VERSION: "{{VERSION}}"
9
+ CURRENT_PROJECT_VERSION: "{{BUILD_NUMBER}}"
10
+ CODE_SIGN_STYLE: Automatic
11
+ DEVELOPMENT_TEAM: "{{TEAM_ID}}"
12
+ configs:
13
+ debug:
14
+ CRAFT_APNS_ENVIRONMENT: development
15
+ release:
16
+ CRAFT_APNS_ENVIRONMENT: production
11
17
  targets:
12
18
  {{APP_NAME}}:
13
19
  type: application
package/dist/mobile.js CHANGED
@@ -15,6 +15,15 @@ function secureUUID() {
15
15
  throw new Error("[Craft] No Web Crypto available; cannot generate secure IDs");
16
16
  }
17
17
 
18
+ // src/api/live-activity-handle.ts
19
+ function createLiveActivityHandle(id, update, end) {
20
+ return {
21
+ id,
22
+ update,
23
+ end
24
+ };
25
+ }
26
+
18
27
  // src/api/mobile.ts
19
28
  var WEB_DEVICE_ID_KEY = "__craft_web_device_id__";
20
29
  function _webFallbackDeviceId() {
@@ -448,11 +457,18 @@ var share = {
448
457
  return craft.share.share(options);
449
458
  }
450
459
  if (navigator.share) {
451
- await navigator.share({
452
- title: options.title,
453
- text: options.text,
454
- url: options.url
455
- });
460
+ try {
461
+ await navigator.share({
462
+ title: options.title,
463
+ text: options.text,
464
+ url: options.url
465
+ });
466
+ return true;
467
+ } catch (error) {
468
+ if (error instanceof DOMException && error.name === "AbortError")
469
+ return false;
470
+ throw error;
471
+ }
456
472
  } else {
457
473
  throw new Error("Share API not available");
458
474
  }
@@ -546,15 +562,16 @@ var deepLinks = {
546
562
  return craft.deepLinks.onLink((value) => {
547
563
  const url = normalizeDeepLinkURL(value);
548
564
  if (url)
549
- callback(url);
565
+ callback(url, { initial: value?.initial === true });
550
566
  }) ?? (() => {});
551
567
  }
552
568
  if (typeof globalThis === "undefined")
553
569
  return () => {};
554
570
  const listener = (event) => {
555
- const url = normalizeDeepLinkURL(event.detail);
571
+ const detail = event.detail;
572
+ const url = normalizeDeepLinkURL(detail);
556
573
  if (url)
557
- callback(url);
574
+ callback(url, { initial: detail?.initial === true });
558
575
  };
559
576
  globalThis.addEventListener("craftDeepLink", listener);
560
577
  return () => globalThis.removeEventListener("craftDeepLink", listener);
@@ -637,25 +654,39 @@ var health = {
637
654
  return craft.health.saveWorkout(workout);
638
655
  }
639
656
  };
657
+ async function updateLiveActivity(idOrState, state) {
658
+ const craft = getCraftRoot();
659
+ if (!craft?.liveActivity?.update)
660
+ return;
661
+ if (typeof idOrState === "string") {
662
+ await craft.liveActivity.update(idOrState, state ?? {});
663
+ return;
664
+ }
665
+ await craft.liveActivity.update(idOrState);
666
+ }
667
+ async function endLiveActivity(id, finalState) {
668
+ const craft = getCraftRoot();
669
+ if (!craft?.liveActivity?.end)
670
+ return;
671
+ if (id !== undefined) {
672
+ await craft.liveActivity.end(id, finalState);
673
+ return;
674
+ }
675
+ await craft.liveActivity.end();
676
+ }
640
677
  var liveActivities = {
641
678
  async start(options) {
642
679
  const craft = getCraftRoot();
643
680
  if (!craft?.liveActivity?.start)
644
681
  throw new Error("Live Activities are unavailable");
645
- return craft.liveActivity.start(options);
646
- },
647
- async update(state) {
648
- const craft = getCraftRoot();
649
- if (!craft?.liveActivity?.update)
650
- return;
651
- await craft.liveActivity.update(state);
682
+ const result = await craft.liveActivity.start(options);
683
+ if (!result || typeof result.id !== "string" || result.id.length === 0) {
684
+ throw new Error("Live Activities returned an invalid activity id");
685
+ }
686
+ return createLiveActivityHandle(result.id, (state) => updateLiveActivity(result.id, state), (finalState) => endLiveActivity(result.id, finalState));
652
687
  },
653
- async end() {
654
- const craft = getCraftRoot();
655
- if (!craft?.liveActivity?.end)
656
- return;
657
- await craft.liveActivity.end();
658
- }
688
+ update: updateLiveActivity,
689
+ end: endLiveActivity
659
690
  };
660
691
  var watchConnectivity = {
661
692
  async send(message) {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Replace monorepo-only or floating Craft SDK ranges in a scaffolded app with
3
+ * the version shipped by the CLI that created it.
4
+ */
5
+ export declare function pinCraftNativeDependency(packagePath: string, craftVersion: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craft-native",
3
- "version": "0.0.90",
3
+ "version": "0.0.92",
4
4
  "type": "module",
5
5
  "description": "Build desktop apps with web languages - TypeScript SDK for Craft",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
@@ -1,383 +0,0 @@
1
- package {{PACKAGE_NAME}}
2
-
3
- import android.app.Activity
4
- import android.content.Context
5
- import androidx.compose.foundation.layout.*
6
- import androidx.compose.material3.*
7
- import androidx.compose.runtime.*
8
- import androidx.compose.ui.Modifier
9
- import androidx.work.*
10
- import com.google.android.material.color.DynamicColors
11
- import com.google.firebase.analytics.FirebaseAnalytics
12
- import com.google.firebase.crashlytics.FirebaseCrashlytics
13
- import com.google.firebase.messaging.FirebaseMessaging
14
- import com.google.firebase.remoteconfig.FirebaseRemoteConfig
15
- import java.util.concurrent.TimeUnit
16
-
17
- /// Android 13+ Advanced Features
18
-
19
- // MARK: - Jetpack Compose Integration
20
-
21
- @Composable
22
- fun CraftComposeApp(content: @Composable () -> Unit) {
23
- MaterialTheme(
24
- colorScheme = dynamicColorScheme(),
25
- typography = Typography(),
26
- content = content
27
- )
28
- }
29
-
30
- @Composable
31
- fun dynamicColorScheme(): ColorScheme {
32
- return if (DynamicColors.isDynamicColorAvailable()) {
33
- dynamicDarkColorScheme()
34
- } else {
35
- darkColorScheme()
36
- }
37
- }
38
-
39
- @Composable
40
- fun CraftWebViewComposable(
41
- url: String,
42
- modifier: Modifier = Modifier
43
- ) {
44
- AndroidView(
45
- factory = { context ->
46
- android.webkit.WebView(context).apply {
47
- settings.javaScriptEnabled = true
48
- loadUrl(url)
49
- }
50
- },
51
- modifier = modifier.fillMaxSize()
52
- )
53
- }
54
-
55
- // MARK: - Material You Dynamic Colors
56
-
57
- class DynamicColorManager(private val activity: Activity) {
58
-
59
- fun applyDynamicColors() {
60
- if (DynamicColors.isDynamicColorAvailable()) {
61
- DynamicColors.applyToActivityIfAvailable(activity)
62
- }
63
- }
64
-
65
- fun isDynamicColorAvailable(): Boolean {
66
- return DynamicColors.isDynamicColorAvailable()
67
- }
68
- }
69
-
70
- // MARK: - Predictive Back Gesture (Android 13+)
71
-
72
- class PredictiveBackHandler(private val activity: Activity) {
73
-
74
- fun enablePredictiveBack() {
75
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
76
- activity.onBackPressedDispatcher.addCallback(activity, object : androidx.activity.OnBackPressedCallback(true) {
77
- override fun handleOnBackPressed() {
78
- // Handle predictive back gesture
79
- // Can animate the exit transition
80
- isEnabled = false
81
- activity.onBackPressedDispatcher.onBackPressed()
82
- }
83
- })
84
- }
85
- }
86
- }
87
-
88
- // MARK: - Per-App Language Preferences (Android 13+)
89
-
90
- class LanguageManager(private val context: Context) {
91
-
92
- fun setAppLanguage(languageCode: String) {
93
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
94
- val localeList = android.os.LocaleList(java.util.Locale.forLanguageTag(languageCode))
95
- context.getSystemService(android.app.LocaleManager::class.java)
96
- ?.applicationLocales = localeList
97
- }
98
- }
99
-
100
- fun getAppLanguage(): String? {
101
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
102
- val locales = context.getSystemService(android.app.LocaleManager::class.java)
103
- ?.applicationLocales
104
- return locales?.get(0)?.toLanguageTag()
105
- }
106
- return null
107
- }
108
- }
109
-
110
- // MARK: - Photo Picker (Android 13+)
111
-
112
- class PhotoPickerManager(private val activity: Activity) {
113
-
114
- fun launchPhotoPicker(maxItems: Int = 1, callback: (List<android.net.Uri>) -> Unit) {
115
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
116
- val intent = android.content.Intent(android.provider.MediaStore.ACTION_PICK_IMAGES).apply {
117
- putExtra(android.provider.MediaStore.EXTRA_PICK_IMAGES_MAX, maxItems)
118
- }
119
- // Launch intent and handle result
120
- }
121
- }
122
- }
123
-
124
- // MARK: - Notification Permission (Android 13+)
125
-
126
- class NotificationPermissionManager(private val activity: Activity) {
127
-
128
- fun requestNotificationPermission(callback: (Boolean) -> Unit) {
129
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
130
- if (androidx.core.app.ActivityCompat.checkSelfPermission(
131
- activity,
132
- android.Manifest.permission.POST_NOTIFICATIONS
133
- ) != android.content.pm.PackageManager.PERMISSION_GRANTED
134
- ) {
135
- androidx.core.app.ActivityCompat.requestPermissions(
136
- activity,
137
- arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
138
- 100
139
- )
140
- } else {
141
- callback(true)
142
- }
143
- } else {
144
- callback(true)
145
- }
146
- }
147
-
148
- fun hasNotificationPermission(): Boolean {
149
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
150
- return androidx.core.app.ActivityCompat.checkSelfPermission(
151
- activity,
152
- android.Manifest.permission.POST_NOTIFICATIONS
153
- ) == android.content.pm.PackageManager.PERMISSION_GRANTED
154
- }
155
- return true
156
- }
157
- }
158
-
159
- // MARK: - Foreground Services
160
-
161
- class ForegroundServiceManager(private val context: Context) {
162
-
163
- fun startForegroundService(
164
- serviceClass: Class<*>,
165
- notificationTitle: String,
166
- notificationText: String
167
- ) {
168
- val intent = android.content.Intent(context, serviceClass)
169
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
170
- context.startForegroundService(intent)
171
- } else {
172
- context.startService(intent)
173
- }
174
- }
175
-
176
- fun stopForegroundService(serviceClass: Class<*>) {
177
- val intent = android.content.Intent(context, serviceClass)
178
- context.stopService(intent)
179
- }
180
- }
181
-
182
- // MARK: - WorkManager for Background Tasks
183
-
184
- class BackgroundTaskManager(private val context: Context) {
185
-
186
- fun scheduleOneTimeWork(
187
- tag: String,
188
- initialDelay: Long = 0,
189
- requiresCharging: Boolean = false,
190
- requiresWifi: Boolean = false
191
- ) {
192
- val constraints = Constraints.Builder()
193
- .setRequiresCharging(requiresCharging)
194
- .setRequiredNetworkType(if (requiresWifi) NetworkType.UNMETERED else NetworkType.CONNECTED)
195
- .build()
196
-
197
- val workRequest = OneTimeWorkRequestBuilder<CraftWorker>()
198
- .setConstraints(constraints)
199
- .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
200
- .addTag(tag)
201
- .build()
202
-
203
- WorkManager.getInstance(context).enqueue(workRequest)
204
- }
205
-
206
- fun schedulePeriodicWork(
207
- tag: String,
208
- intervalMinutes: Long,
209
- requiresCharging: Boolean = false
210
- ) {
211
- val constraints = Constraints.Builder()
212
- .setRequiresCharging(requiresCharging)
213
- .build()
214
-
215
- val workRequest = PeriodicWorkRequestBuilder<CraftWorker>(
216
- intervalMinutes, TimeUnit.MINUTES
217
- )
218
- .setConstraints(constraints)
219
- .addTag(tag)
220
- .build()
221
-
222
- WorkManager.getInstance(context).enqueue(workRequest)
223
- }
224
-
225
- fun cancelWork(tag: String) {
226
- WorkManager.getInstance(context).cancelAllWorkByTag(tag)
227
- }
228
-
229
- class CraftWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
230
- override fun doWork(): Result {
231
- // Perform background task
232
- return Result.success()
233
- }
234
- }
235
- }
236
-
237
- // MARK: - Firebase Integration
238
-
239
- class FirebaseManager(private val context: Context) {
240
-
241
- private val analytics: FirebaseAnalytics by lazy {
242
- FirebaseAnalytics.getInstance(context)
243
- }
244
-
245
- private val crashlytics: FirebaseCrashlytics by lazy {
246
- FirebaseCrashlytics.getInstance()
247
- }
248
-
249
- private val remoteConfig: FirebaseRemoteConfig by lazy {
250
- FirebaseRemoteConfig.getInstance()
251
- }
252
-
253
- // Analytics
254
- fun logEvent(eventName: String, params: Map<String, Any>) {
255
- val bundle = android.os.Bundle().apply {
256
- params.forEach { (key, value) ->
257
- when (value) {
258
- is String -> putString(key, value)
259
- is Int -> putInt(key, value)
260
- is Long -> putLong(key, value)
261
- is Double -> putDouble(key, value)
262
- is Boolean -> putBoolean(key, value)
263
- }
264
- }
265
- }
266
- analytics.logEvent(eventName, bundle)
267
- }
268
-
269
- fun setUserProperty(name: String, value: String) {
270
- analytics.setUserProperty(name, value)
271
- }
272
-
273
- // Crashlytics
274
- fun logCrash(message: String) {
275
- crashlytics.log(message)
276
- }
277
-
278
- fun recordException(throwable: Throwable) {
279
- crashlytics.recordException(throwable)
280
- }
281
-
282
- fun setUserId(userId: String) {
283
- crashlytics.setUserId(userId)
284
- }
285
-
286
- // Remote Config
287
- fun fetchRemoteConfig(callback: (Boolean) -> Unit) {
288
- remoteConfig.fetchAndActivate()
289
- .addOnCompleteListener { task ->
290
- callback(task.isSuccessful)
291
- }
292
- }
293
-
294
- fun getString(key: String): String {
295
- return remoteConfig.getString(key)
296
- }
297
-
298
- fun getBoolean(key: String): Boolean {
299
- return remoteConfig.getBoolean(key)
300
- }
301
-
302
- fun getLong(key: String): Long {
303
- return remoteConfig.getLong(key)
304
- }
305
-
306
- // FCM
307
- fun getFirebaseToken(callback: (String?) -> Unit) {
308
- FirebaseMessaging.getInstance().token
309
- .addOnCompleteListener { task ->
310
- if (task.isSuccessful) {
311
- callback(task.result)
312
- } else {
313
- callback(null)
314
- }
315
- }
316
- }
317
-
318
- fun subscribeToTopic(topic: String) {
319
- FirebaseMessaging.getInstance().subscribeToTopic(topic)
320
- }
321
-
322
- fun unsubscribeFromTopic(topic: String) {
323
- FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
324
- }
325
- }
326
-
327
- // MARK: - Bottom Sheet
328
-
329
- @OptIn(ExperimentalMaterial3Api::class)
330
- @Composable
331
- fun CraftBottomSheet(
332
- sheetContent: @Composable ColumnScope.() -> Unit,
333
- content: @Composable () -> Unit
334
- ) {
335
- val sheetState = rememberModalBottomSheetState()
336
- var showBottomSheet by remember { mutableStateOf(false) }
337
-
338
- if (showBottomSheet) {
339
- ModalBottomSheet(
340
- onDismissRequest = { showBottomSheet = false },
341
- sheetState = sheetState
342
- ) {
343
- sheetContent()
344
- }
345
- }
346
-
347
- content()
348
- }
349
-
350
- // MARK: - Navigation Drawer
351
-
352
- @Composable
353
- fun CraftNavigationDrawer(
354
- drawerContent: @Composable ColumnScope.() -> Unit,
355
- content: @Composable () -> Unit
356
- ) {
357
- val drawerState = rememberDrawerState(DrawerValue.Closed)
358
- val scope = rememberCoroutineScope()
359
-
360
- ModalNavigationDrawer(
361
- drawerState = drawerState,
362
- drawerContent = {
363
- ModalDrawerSheet {
364
- drawerContent()
365
- }
366
- }
367
- ) {
368
- content()
369
- }
370
- }
371
-
372
- // MARK: - Bridge Integration
373
-
374
- fun CraftBridge.initializeAdvancedFeatures() {
375
- // Initialize all advanced Android features
376
- DynamicColorManager(activity).applyDynamicColors()
377
- PredictiveBackHandler(activity).enablePredictiveBack()
378
- }
379
-
380
- fun CraftBridge.setupFirebase() {
381
- // Setup Firebase services
382
- // Called from MainActivity onCreate
383
- }