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.
- package/dist/android/src/index.d.ts +36 -1
- package/dist/android/src/index.js +332 -44
- package/dist/android/src/promise-runtime.d.ts +3 -0
- package/dist/android/templates/CraftBridge.kt.template +2167 -1177
- 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 +2250 -0
- package/dist/android/templates/LocationRecordingService.kt.template +34 -9
- package/dist/android/templates/MainActivity.kt.template +42 -8
- 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 +25 -7
- package/dist/api/window.d.ts +2 -0
- package/dist/cli.js +452 -129
- package/dist/index.cjs +77 -22
- package/dist/index.js +77 -22
- package/dist/ios/src/index.d.ts +1 -1
- package/dist/ios/src/index.js +23 -5
- package/dist/ios/templates/CraftApp.swift +706 -92
- 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 +52 -21
- 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
|
@@ -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 {
|
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
|
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
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
654
|
-
|
|
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) {
|
package/package.json
CHANGED
|
@@ -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
|
-
}
|