craft-native 0.0.37 → 0.0.49
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 +268 -0
- package/dist/android/src/index.js.map +10 -0
- package/dist/android/templates/AndroidManifest.xml.template +69 -0
- package/dist/android/templates/CraftBridge.kt.template +3748 -0
- package/dist/android/templates/CraftBridgeExtensions.kt.template +383 -0
- package/dist/android/templates/CraftWidgetProvider.kt.template +246 -0
- package/dist/android/templates/MainActivity.kt.template +109 -0
- package/dist/android/templates/build.gradle.kts.app.template +80 -0
- package/dist/android/templates/build.gradle.kts.project.template +5 -0
- package/dist/android/templates/fastlane/Appfile +25 -0
- package/dist/android/templates/fastlane/Fastfile +192 -0
- package/dist/android/templates/fastlane/Gemfile +7 -0
- package/dist/android/templates/github-workflow-android.yml +248 -0
- package/dist/android/templates/github-workflow-test.yml +329 -0
- package/dist/android/templates/settings.gradle.kts.template +18 -0
- package/dist/android/templates/test-bridges.html +1463 -0
- package/dist/api/window.d.ts +18 -0
- package/dist/api/window.d.ts.map +1 -1
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/bridge/core.d.ts +2 -0
- package/dist/bridge/core.d.ts.map +1 -1
- package/dist/cli.js +4168 -0
- package/dist/components/sidebar.d.ts +5 -1
- package/dist/components/sidebar.d.ts.map +1 -1
- package/dist/dev/devtools.d.ts +1 -1
- package/dist/dev/devtools.d.ts.map +1 -1
- package/dist/dev/hot-reload.d.ts +12 -3
- package/dist/dev/hot-reload.d.ts.map +1 -1
- package/dist/index.cjs +573 -208
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +585 -216
- package/dist/index.js.map +45 -0
- package/dist/ios/src/index.js +186 -0
- package/dist/ios/src/index.js.map +10 -0
- package/dist/ios/templates/CraftApp.swift +4241 -0
- package/dist/ios/templates/CraftAppExtensions.swift +361 -0
- package/dist/ios/templates/CraftWidget.swift +247 -0
- package/dist/ios/templates/Info.plist.template +65 -0
- package/dist/ios/templates/fastlane/Appfile +45 -0
- package/dist/ios/templates/fastlane/Fastfile +189 -0
- package/dist/ios/templates/fastlane/Gemfile +7 -0
- package/dist/ios/templates/fastlane/Matchfile +58 -0
- package/dist/ios/templates/github-workflow-ios.yml +192 -0
- package/dist/ios/templates/github-workflow-release.yml +175 -0
- package/dist/ios/templates/github-workflow-test.yml +272 -0
- package/dist/ios/templates/project.yml.template +25 -0
- package/dist/ios/templates/test-bridges.html +1463 -0
- package/dist/package.d.ts +106 -2
- package/dist/package.d.ts.map +1 -1
- package/dist/package.test.d.ts +2 -0
- package/dist/package.test.d.ts.map +1 -0
- package/dist/styles/sidebar-templates.d.ts.map +1 -1
- package/dist/styles/sidebars.d.ts +12 -8
- package/dist/styles/sidebars.d.ts.map +1 -1
- package/dist/templates/projects/blank/craft.config.ts +19 -0
- package/dist/templates/projects/blank/index.html +27 -0
- package/dist/templates/projects/blank/package.json +19 -0
- package/dist/templates/projects/blank/src/main.ts +26 -0
- package/dist/templates/projects/dashboard/craft.config.ts +42 -0
- package/dist/templates/projects/dashboard/index.html +13 -0
- package/dist/templates/projects/dashboard/package.json +20 -0
- package/dist/templates/projects/dashboard/src/main.ts +163 -0
- package/dist/templates/projects/dashboard/src/styles.css +280 -0
- package/dist/templates/projects/drawer/craft.config.ts +47 -0
- package/dist/templates/projects/drawer/index.html +13 -0
- package/dist/templates/projects/drawer/package.json +21 -0
- package/dist/templates/projects/drawer/src/main.ts +406 -0
- package/dist/templates/projects/drawer/src/styles.css +468 -0
- package/dist/templates/projects/ecommerce/craft.config.ts +32 -0
- package/dist/templates/projects/ecommerce/index.html +13 -0
- package/dist/templates/projects/ecommerce/package.json +19 -0
- package/dist/templates/projects/ecommerce/src/main.ts +194 -0
- package/dist/templates/projects/ecommerce/src/styles.css +169 -0
- package/dist/templates/projects/social/craft.config.ts +32 -0
- package/dist/templates/projects/social/index.html +13 -0
- package/dist/templates/projects/social/package.json +19 -0
- package/dist/templates/projects/social/src/main.ts +270 -0
- package/dist/templates/projects/social/src/styles.css +216 -0
- package/dist/templates/projects/tabs/craft.config.ts +47 -0
- package/dist/templates/projects/tabs/index.html +13 -0
- package/dist/templates/projects/tabs/package.json +21 -0
- package/dist/templates/projects/tabs/src/main.ts +177 -0
- package/dist/templates/projects/tabs/src/styles.css +163 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/updater/index.d.ts +1 -0
- package/dist/updater/index.d.ts.map +1 -1
- package/package.json +5 -12
- package/bin/cli.ts +0 -937
|
@@ -0,0 +1,383 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
package {{PACKAGE_NAME}}
|
|
2
|
+
|
|
3
|
+
import android.app.PendingIntent
|
|
4
|
+
import android.appwidget.AppWidgetManager
|
|
5
|
+
import android.appwidget.AppWidgetProvider
|
|
6
|
+
import android.content.BroadcastReceiver
|
|
7
|
+
import android.content.ComponentName
|
|
8
|
+
import android.content.Context
|
|
9
|
+
import android.content.Intent
|
|
10
|
+
import android.content.IntentFilter
|
|
11
|
+
import android.os.Build
|
|
12
|
+
import android.widget.RemoteViews
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Craft Widget Provider
|
|
16
|
+
*
|
|
17
|
+
* A customizable home screen widget that displays data from your Craft app.
|
|
18
|
+
* The widget can show title, subtitle, value, and an icon.
|
|
19
|
+
*
|
|
20
|
+
* To use widgets:
|
|
21
|
+
* 1. Add this provider to your AndroidManifest.xml
|
|
22
|
+
* 2. Create widget layout in res/layout/craft_widget.xml
|
|
23
|
+
* 3. Create widget info in res/xml/craft_widget_info.xml
|
|
24
|
+
* 4. Call window.craft.widget.update() from your web app
|
|
25
|
+
*/
|
|
26
|
+
class CraftWidgetProvider : AppWidgetProvider() {
|
|
27
|
+
|
|
28
|
+
companion object {
|
|
29
|
+
const val ACTION_WIDGET_UPDATE = "{{PACKAGE_NAME}}.WIDGET_UPDATE"
|
|
30
|
+
const val PREFS_NAME = "craft_widget_prefs"
|
|
31
|
+
const val PREF_TITLE = "widget_title"
|
|
32
|
+
const val PREF_SUBTITLE = "widget_subtitle"
|
|
33
|
+
const val PREF_VALUE = "widget_value"
|
|
34
|
+
const val PREF_ICON = "widget_icon"
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Update all widgets programmatically
|
|
38
|
+
*/
|
|
39
|
+
fun updateAllWidgets(context: Context) {
|
|
40
|
+
val intent = Intent(context, CraftWidgetProvider::class.java).apply {
|
|
41
|
+
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
|
42
|
+
val appWidgetManager = AppWidgetManager.getInstance(context)
|
|
43
|
+
val widgetComponent = ComponentName(context, CraftWidgetProvider::class.java)
|
|
44
|
+
val appWidgetIds = appWidgetManager.getAppWidgetIds(widgetComponent)
|
|
45
|
+
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
|
|
46
|
+
}
|
|
47
|
+
context.sendBroadcast(intent)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun onUpdate(
|
|
52
|
+
context: Context,
|
|
53
|
+
appWidgetManager: AppWidgetManager,
|
|
54
|
+
appWidgetIds: IntArray
|
|
55
|
+
) {
|
|
56
|
+
// Update each widget instance
|
|
57
|
+
for (appWidgetId in appWidgetIds) {
|
|
58
|
+
updateAppWidget(context, appWidgetManager, appWidgetId)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
63
|
+
super.onReceive(context, intent)
|
|
64
|
+
|
|
65
|
+
// Handle custom update action from the app
|
|
66
|
+
if (intent.action == ACTION_WIDGET_UPDATE) {
|
|
67
|
+
val appWidgetManager = AppWidgetManager.getInstance(context)
|
|
68
|
+
val widgetComponent = ComponentName(context, CraftWidgetProvider::class.java)
|
|
69
|
+
val appWidgetIds = appWidgetManager.getAppWidgetIds(widgetComponent)
|
|
70
|
+
|
|
71
|
+
for (appWidgetId in appWidgetIds) {
|
|
72
|
+
updateAppWidget(context, appWidgetManager, appWidgetId)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
override fun onEnabled(context: Context) {
|
|
78
|
+
// Register broadcast receiver for widget updates
|
|
79
|
+
val filter = IntentFilter(ACTION_WIDGET_UPDATE)
|
|
80
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
81
|
+
context.registerReceiver(widgetUpdateReceiver, filter, Context.RECEIVER_EXPORTED)
|
|
82
|
+
} else {
|
|
83
|
+
context.registerReceiver(widgetUpdateReceiver, filter)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun onDisabled(context: Context) {
|
|
88
|
+
// Unregister broadcast receiver
|
|
89
|
+
try {
|
|
90
|
+
context.unregisterReceiver(widgetUpdateReceiver)
|
|
91
|
+
} catch (_: IllegalArgumentException) {
|
|
92
|
+
// Receiver not registered
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private val widgetUpdateReceiver = object : BroadcastReceiver() {
|
|
97
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
98
|
+
if (intent.action == ACTION_WIDGET_UPDATE) {
|
|
99
|
+
updateAllWidgets(context)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private fun updateAppWidget(
|
|
105
|
+
context: Context,
|
|
106
|
+
appWidgetManager: AppWidgetManager,
|
|
107
|
+
appWidgetId: Int
|
|
108
|
+
) {
|
|
109
|
+
// Get data from shared preferences
|
|
110
|
+
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
111
|
+
val title = prefs.getString(PREF_TITLE, "Craft Widget") ?: "Craft Widget"
|
|
112
|
+
val subtitle = prefs.getString(PREF_SUBTITLE, "") ?: ""
|
|
113
|
+
val value = prefs.getString(PREF_VALUE, "") ?: ""
|
|
114
|
+
val iconName = prefs.getString(PREF_ICON, null)
|
|
115
|
+
|
|
116
|
+
// Create remote views
|
|
117
|
+
val views = RemoteViews(context.packageName, R.layout.craft_widget)
|
|
118
|
+
|
|
119
|
+
// Set text values
|
|
120
|
+
views.setTextViewText(R.id.widget_title, title)
|
|
121
|
+
views.setTextViewText(R.id.widget_subtitle, subtitle)
|
|
122
|
+
views.setTextViewText(R.id.widget_value, value)
|
|
123
|
+
|
|
124
|
+
// Set visibility for subtitle
|
|
125
|
+
views.setViewVisibility(
|
|
126
|
+
R.id.widget_subtitle,
|
|
127
|
+
if (subtitle.isEmpty()) android.view.View.GONE else android.view.View.VISIBLE
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Set visibility for value
|
|
131
|
+
views.setViewVisibility(
|
|
132
|
+
R.id.widget_value,
|
|
133
|
+
if (value.isEmpty()) android.view.View.GONE else android.view.View.VISIBLE
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// Set icon if provided
|
|
137
|
+
if (iconName != null) {
|
|
138
|
+
val iconResId = context.resources.getIdentifier(
|
|
139
|
+
iconName,
|
|
140
|
+
"drawable",
|
|
141
|
+
context.packageName
|
|
142
|
+
)
|
|
143
|
+
if (iconResId != 0) {
|
|
144
|
+
views.setImageViewResource(R.id.widget_icon, iconResId)
|
|
145
|
+
views.setViewVisibility(R.id.widget_icon, android.view.View.VISIBLE)
|
|
146
|
+
} else {
|
|
147
|
+
views.setViewVisibility(R.id.widget_icon, android.view.View.GONE)
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
views.setViewVisibility(R.id.widget_icon, android.view.View.GONE)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Create click intent to launch main app
|
|
154
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
155
|
+
if (launchIntent != null) {
|
|
156
|
+
val pendingIntent = PendingIntent.getActivity(
|
|
157
|
+
context,
|
|
158
|
+
0,
|
|
159
|
+
launchIntent,
|
|
160
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
161
|
+
)
|
|
162
|
+
views.setOnClickPendingIntent(R.id.widget_container, pendingIntent)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Update the widget
|
|
166
|
+
appWidgetManager.updateAppWidget(appWidgetId, views)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Widget Layout (res/layout/craft_widget.xml):
|
|
172
|
+
*
|
|
173
|
+
* <?xml version="1.0" encoding="utf-8"?>
|
|
174
|
+
* <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
175
|
+
* android:id="@+id/widget_container"
|
|
176
|
+
* android:layout_width="match_parent"
|
|
177
|
+
* android:layout_height="match_parent"
|
|
178
|
+
* android:orientation="vertical"
|
|
179
|
+
* android:padding="16dp"
|
|
180
|
+
* android:background="@drawable/widget_background">
|
|
181
|
+
*
|
|
182
|
+
* <ImageView
|
|
183
|
+
* android:id="@+id/widget_icon"
|
|
184
|
+
* android:layout_width="32dp"
|
|
185
|
+
* android:layout_height="32dp"
|
|
186
|
+
* android:visibility="gone" />
|
|
187
|
+
*
|
|
188
|
+
* <TextView
|
|
189
|
+
* android:id="@+id/widget_title"
|
|
190
|
+
* android:layout_width="wrap_content"
|
|
191
|
+
* android:layout_height="wrap_content"
|
|
192
|
+
* android:textSize="16sp"
|
|
193
|
+
* android:textStyle="bold"
|
|
194
|
+
* android:textColor="@color/widget_text_primary" />
|
|
195
|
+
*
|
|
196
|
+
* <TextView
|
|
197
|
+
* android:id="@+id/widget_subtitle"
|
|
198
|
+
* android:layout_width="wrap_content"
|
|
199
|
+
* android:layout_height="wrap_content"
|
|
200
|
+
* android:textSize="12sp"
|
|
201
|
+
* android:textColor="@color/widget_text_secondary" />
|
|
202
|
+
*
|
|
203
|
+
* <TextView
|
|
204
|
+
* android:id="@+id/widget_value"
|
|
205
|
+
* android:layout_width="wrap_content"
|
|
206
|
+
* android:layout_height="wrap_content"
|
|
207
|
+
* android:textSize="32sp"
|
|
208
|
+
* android:textStyle="bold"
|
|
209
|
+
* android:textColor="@color/widget_accent"
|
|
210
|
+
* android:layout_marginTop="8dp" />
|
|
211
|
+
*
|
|
212
|
+
* </LinearLayout>
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Widget Info (res/xml/craft_widget_info.xml):
|
|
217
|
+
*
|
|
218
|
+
* <?xml version="1.0" encoding="utf-8"?>
|
|
219
|
+
* <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
|
220
|
+
* android:minWidth="110dp"
|
|
221
|
+
* android:minHeight="110dp"
|
|
222
|
+
* android:targetCellWidth="2"
|
|
223
|
+
* android:targetCellHeight="2"
|
|
224
|
+
* android:updatePeriodMillis="1800000"
|
|
225
|
+
* android:initialLayout="@layout/craft_widget"
|
|
226
|
+
* android:resizeMode="horizontal|vertical"
|
|
227
|
+
* android:widgetCategory="home_screen"
|
|
228
|
+
* android:previewImage="@drawable/widget_preview"
|
|
229
|
+
* android:description="@string/widget_description" />
|
|
230
|
+
*/
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* AndroidManifest.xml entries needed:
|
|
234
|
+
*
|
|
235
|
+
* <receiver
|
|
236
|
+
* android:name=".CraftWidgetProvider"
|
|
237
|
+
* android:exported="true">
|
|
238
|
+
* <intent-filter>
|
|
239
|
+
* <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
|
240
|
+
* <action android:name="{{PACKAGE_NAME}}.WIDGET_UPDATE" />
|
|
241
|
+
* </intent-filter>
|
|
242
|
+
* <meta-data
|
|
243
|
+
* android:name="android.appwidget.provider"
|
|
244
|
+
* android:resource="@xml/craft_widget_info" />
|
|
245
|
+
* </receiver>
|
|
246
|
+
*/
|