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.
Files changed (89) hide show
  1. package/dist/android/src/index.js +268 -0
  2. package/dist/android/src/index.js.map +10 -0
  3. package/dist/android/templates/AndroidManifest.xml.template +69 -0
  4. package/dist/android/templates/CraftBridge.kt.template +3748 -0
  5. package/dist/android/templates/CraftBridgeExtensions.kt.template +383 -0
  6. package/dist/android/templates/CraftWidgetProvider.kt.template +246 -0
  7. package/dist/android/templates/MainActivity.kt.template +109 -0
  8. package/dist/android/templates/build.gradle.kts.app.template +80 -0
  9. package/dist/android/templates/build.gradle.kts.project.template +5 -0
  10. package/dist/android/templates/fastlane/Appfile +25 -0
  11. package/dist/android/templates/fastlane/Fastfile +192 -0
  12. package/dist/android/templates/fastlane/Gemfile +7 -0
  13. package/dist/android/templates/github-workflow-android.yml +248 -0
  14. package/dist/android/templates/github-workflow-test.yml +329 -0
  15. package/dist/android/templates/settings.gradle.kts.template +18 -0
  16. package/dist/android/templates/test-bridges.html +1463 -0
  17. package/dist/api/window.d.ts +18 -0
  18. package/dist/api/window.d.ts.map +1 -1
  19. package/dist/assets/index.d.ts.map +1 -1
  20. package/dist/bridge/core.d.ts +2 -0
  21. package/dist/bridge/core.d.ts.map +1 -1
  22. package/dist/cli.js +4168 -0
  23. package/dist/components/sidebar.d.ts +5 -1
  24. package/dist/components/sidebar.d.ts.map +1 -1
  25. package/dist/dev/devtools.d.ts +1 -1
  26. package/dist/dev/devtools.d.ts.map +1 -1
  27. package/dist/dev/hot-reload.d.ts +12 -3
  28. package/dist/dev/hot-reload.d.ts.map +1 -1
  29. package/dist/index.cjs +573 -208
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +585 -216
  32. package/dist/index.js.map +45 -0
  33. package/dist/ios/src/index.js +186 -0
  34. package/dist/ios/src/index.js.map +10 -0
  35. package/dist/ios/templates/CraftApp.swift +4241 -0
  36. package/dist/ios/templates/CraftAppExtensions.swift +361 -0
  37. package/dist/ios/templates/CraftWidget.swift +247 -0
  38. package/dist/ios/templates/Info.plist.template +65 -0
  39. package/dist/ios/templates/fastlane/Appfile +45 -0
  40. package/dist/ios/templates/fastlane/Fastfile +189 -0
  41. package/dist/ios/templates/fastlane/Gemfile +7 -0
  42. package/dist/ios/templates/fastlane/Matchfile +58 -0
  43. package/dist/ios/templates/github-workflow-ios.yml +192 -0
  44. package/dist/ios/templates/github-workflow-release.yml +175 -0
  45. package/dist/ios/templates/github-workflow-test.yml +272 -0
  46. package/dist/ios/templates/project.yml.template +25 -0
  47. package/dist/ios/templates/test-bridges.html +1463 -0
  48. package/dist/package.d.ts +106 -2
  49. package/dist/package.d.ts.map +1 -1
  50. package/dist/package.test.d.ts +2 -0
  51. package/dist/package.test.d.ts.map +1 -0
  52. package/dist/styles/sidebar-templates.d.ts.map +1 -1
  53. package/dist/styles/sidebars.d.ts +12 -8
  54. package/dist/styles/sidebars.d.ts.map +1 -1
  55. package/dist/templates/projects/blank/craft.config.ts +19 -0
  56. package/dist/templates/projects/blank/index.html +27 -0
  57. package/dist/templates/projects/blank/package.json +19 -0
  58. package/dist/templates/projects/blank/src/main.ts +26 -0
  59. package/dist/templates/projects/dashboard/craft.config.ts +42 -0
  60. package/dist/templates/projects/dashboard/index.html +13 -0
  61. package/dist/templates/projects/dashboard/package.json +20 -0
  62. package/dist/templates/projects/dashboard/src/main.ts +163 -0
  63. package/dist/templates/projects/dashboard/src/styles.css +280 -0
  64. package/dist/templates/projects/drawer/craft.config.ts +47 -0
  65. package/dist/templates/projects/drawer/index.html +13 -0
  66. package/dist/templates/projects/drawer/package.json +21 -0
  67. package/dist/templates/projects/drawer/src/main.ts +406 -0
  68. package/dist/templates/projects/drawer/src/styles.css +468 -0
  69. package/dist/templates/projects/ecommerce/craft.config.ts +32 -0
  70. package/dist/templates/projects/ecommerce/index.html +13 -0
  71. package/dist/templates/projects/ecommerce/package.json +19 -0
  72. package/dist/templates/projects/ecommerce/src/main.ts +194 -0
  73. package/dist/templates/projects/ecommerce/src/styles.css +169 -0
  74. package/dist/templates/projects/social/craft.config.ts +32 -0
  75. package/dist/templates/projects/social/index.html +13 -0
  76. package/dist/templates/projects/social/package.json +19 -0
  77. package/dist/templates/projects/social/src/main.ts +270 -0
  78. package/dist/templates/projects/social/src/styles.css +216 -0
  79. package/dist/templates/projects/tabs/craft.config.ts +47 -0
  80. package/dist/templates/projects/tabs/index.html +13 -0
  81. package/dist/templates/projects/tabs/package.json +21 -0
  82. package/dist/templates/projects/tabs/src/main.ts +177 -0
  83. package/dist/templates/projects/tabs/src/styles.css +163 -0
  84. package/dist/types.d.ts +29 -0
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/updater/index.d.ts +1 -0
  87. package/dist/updater/index.d.ts.map +1 -1
  88. package/package.json +5 -12
  89. 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
+ */