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.
Files changed (29) hide show
  1. package/dist/android/src/index.js +285 -44
  2. package/dist/android/src/promise-runtime.d.ts +3 -0
  3. package/dist/android/templates/CraftBridge.kt.template +1951 -1193
  4. package/dist/android/templates/CraftHealthConnect.kt.template +45 -6
  5. package/dist/android/templates/CraftHealthConnectStub.kt.template +7 -1
  6. package/dist/android/templates/CraftNative.kt.template +2231 -0
  7. package/dist/android/templates/LocationRecordingService.kt.template +34 -9
  8. package/dist/android/templates/MainActivity.kt.template +25 -2
  9. package/dist/android/templates/proguard-rules.pro.template +4 -1
  10. package/dist/android/templates/test-bridges.html +10 -33
  11. package/dist/api/index.d.ts +1 -1
  12. package/dist/api/ios-advanced.d.ts +8 -5
  13. package/dist/api/live-activity-handle.d.ts +6 -0
  14. package/dist/api/mobile.d.ts +13 -5
  15. package/dist/api/window.d.ts +2 -0
  16. package/dist/cli.js +404 -128
  17. package/dist/index.cjs +65 -17
  18. package/dist/index.js +65 -17
  19. package/dist/ios/src/index.js +22 -4
  20. package/dist/ios/templates/CraftApp.swift +473 -60
  21. package/dist/ios/templates/CraftWatchApp.swift.template +8 -0
  22. package/dist/ios/templates/WatchApp.Info.plist.template +2 -0
  23. package/dist/ios/templates/WatchExtension.Info.plist.template +34 -0
  24. package/dist/ios/templates/project.yml.template +10 -4
  25. package/dist/mobile.js +36 -13
  26. package/dist/scaffold-version.d.ts +5 -0
  27. package/package.json +1 -1
  28. package/dist/android/templates/CraftBridgeExtensions.kt.template +0 -383
  29. 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() {
@@ -637,25 +646,39 @@ var health = {
637
646
  return craft.health.saveWorkout(workout);
638
647
  }
639
648
  };
649
+ async function updateLiveActivity(idOrState, state) {
650
+ const craft = getCraftRoot();
651
+ if (!craft?.liveActivity?.update)
652
+ return;
653
+ if (typeof idOrState === "string") {
654
+ await craft.liveActivity.update(idOrState, state ?? {});
655
+ return;
656
+ }
657
+ await craft.liveActivity.update(idOrState);
658
+ }
659
+ async function endLiveActivity(id, finalState) {
660
+ const craft = getCraftRoot();
661
+ if (!craft?.liveActivity?.end)
662
+ return;
663
+ if (id !== undefined) {
664
+ await craft.liveActivity.end(id, finalState);
665
+ return;
666
+ }
667
+ await craft.liveActivity.end();
668
+ }
640
669
  var liveActivities = {
641
670
  async start(options) {
642
671
  const craft = getCraftRoot();
643
672
  if (!craft?.liveActivity?.start)
644
673
  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);
674
+ const result = await craft.liveActivity.start(options);
675
+ if (!result || typeof result.id !== "string" || result.id.length === 0) {
676
+ throw new Error("Live Activities returned an invalid activity id");
677
+ }
678
+ return createLiveActivityHandle(result.id, (state) => updateLiveActivity(result.id, state), (finalState) => endLiveActivity(result.id, finalState));
652
679
  },
653
- async end() {
654
- const craft = getCraftRoot();
655
- if (!craft?.liveActivity?.end)
656
- return;
657
- await craft.liveActivity.end();
658
- }
680
+ update: updateLiveActivity,
681
+ end: endLiveActivity
659
682
  };
660
683
  var watchConnectivity = {
661
684
  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.89",
3
+ "version": "0.0.91",
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
- }