nn-widgets 0.1.4 → 0.1.13

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.
@@ -0,0 +1,126 @@
1
+ package expo.modules.nnwidgets
2
+
3
+ import android.appwidget.AppWidgetManager
4
+ import android.content.ComponentName
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.content.SharedPreferences
8
+ import androidx.core.os.bundleOf
9
+ import expo.modules.kotlin.modules.Module
10
+ import expo.modules.kotlin.modules.ModuleDefinition
11
+ import expo.modules.kotlin.Promise
12
+ import org.json.JSONObject
13
+
14
+ class NNWidgetsModule : Module() {
15
+
16
+ companion object {
17
+ const val PREFS_NAME = "nn_widgets_data"
18
+ const val WIDGET_DATA_KEYS = "widget_data_keys"
19
+ }
20
+
21
+ private val context get() = requireNotNull(appContext.reactContext)
22
+
23
+ private fun getPreferences(): SharedPreferences {
24
+ return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
25
+ }
26
+
27
+ override fun definition() = ModuleDefinition {
28
+ Name("NNWidgets")
29
+
30
+ // Check if widgets are supported (Android 4.0+, API 14+)
31
+ Function("isSupported") {
32
+ true // Widgets are supported on all modern Android versions
33
+ }
34
+
35
+ // Set widget data to SharedPreferences
36
+ AsyncFunction("setWidgetData") { data: Map<String, Any?>, promise: Promise ->
37
+ try {
38
+ val prefs = getPreferences()
39
+ val editor = prefs.edit()
40
+ val keys = mutableListOf<String>()
41
+
42
+ for ((key, value) in data) {
43
+ val prefKey = "widget_$key"
44
+ keys.add(prefKey)
45
+
46
+ when (value) {
47
+ is String -> editor.putString(prefKey, value)
48
+ is Int -> editor.putInt(prefKey, value)
49
+ is Long -> editor.putLong(prefKey, value)
50
+ is Float -> editor.putFloat(prefKey, value)
51
+ is Double -> editor.putFloat(prefKey, value.toFloat())
52
+ is Boolean -> editor.putBoolean(prefKey, value)
53
+ null -> editor.remove(prefKey)
54
+ else -> editor.putString(prefKey, value.toString())
55
+ }
56
+ }
57
+
58
+ // Store keys list
59
+ editor.putStringSet(WIDGET_DATA_KEYS, keys.toSet())
60
+ editor.apply()
61
+
62
+ println("[NNWidgets] Widget data saved: ${keys.joinToString(", ")}")
63
+ promise.resolve(true)
64
+ } catch (e: Exception) {
65
+ println("[NNWidgets] Failed to save widget data: ${e.message}")
66
+ promise.resolve(false)
67
+ }
68
+ }
69
+
70
+ // Get widget data from SharedPreferences
71
+ AsyncFunction("getWidgetData") { promise: Promise ->
72
+ try {
73
+ val prefs = getPreferences()
74
+ val keys = prefs.getStringSet(WIDGET_DATA_KEYS, emptySet()) ?: emptySet()
75
+ val result = mutableMapOf<String, Any?>()
76
+
77
+ for (key in keys) {
78
+ val cleanKey = key.removePrefix("widget_")
79
+ val value = prefs.all[key]
80
+ if (value != null) {
81
+ result[cleanKey] = value
82
+ }
83
+ }
84
+
85
+ promise.resolve(result)
86
+ } catch (e: Exception) {
87
+ println("[NNWidgets] Failed to get widget data: ${e.message}")
88
+ promise.resolve(emptyMap<String, Any?>())
89
+ }
90
+ }
91
+
92
+ // Request widget update
93
+ AsyncFunction("reloadWidget") { widgetClassName: String?, promise: Promise ->
94
+ try {
95
+ val appWidgetManager = AppWidgetManager.getInstance(context)
96
+
97
+ if (widgetClassName != null) {
98
+ // Update specific widget
99
+ val componentName = ComponentName(context.packageName, widgetClassName)
100
+ val widgetIds = appWidgetManager.getAppWidgetIds(componentName)
101
+
102
+ if (widgetIds.isNotEmpty()) {
103
+ val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply {
104
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds)
105
+ component = componentName
106
+ }
107
+ context.sendBroadcast(intent)
108
+ println("[NNWidgets] Reloaded widget: $widgetClassName")
109
+ }
110
+ } else {
111
+ // Broadcast update to all widgets in the app
112
+ // This requires knowing the widget class names
113
+ // For now, we'll use a custom broadcast that widgets can listen to
114
+ val intent = Intent("${context.packageName}.WIDGET_UPDATE")
115
+ context.sendBroadcast(intent)
116
+ println("[NNWidgets] Sent update broadcast to all widgets")
117
+ }
118
+
119
+ promise.resolve(true)
120
+ } catch (e: Exception) {
121
+ println("[NNWidgets] Failed to reload widget: ${e.message}")
122
+ promise.resolve(false)
123
+ }
124
+ }
125
+ }
126
+ }
@@ -4,6 +4,7 @@
4
4
  "modules": ["NNWidgetsModule"]
5
5
  },
6
6
  "android": {
7
- "modules": ["expo.modules.nnwidgets.NNWidgetsModule"]
7
+ "modules": ["expo.modules.nnwidgets.NNWidgetsModule"],
8
+ "packageImportPath": "expo.modules.nnwidgets"
8
9
  }
9
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nn-widgets",
3
- "version": "0.1.4",
3
+ "version": "0.1.13",
4
4
  "description": "Expo config plugin for adding native widgets (iOS WidgetKit & Android App Widgets)",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -927,24 +927,24 @@ struct ${w.name}Provider: TimelineProvider {
927
927
 
928
928
  struct ${w.name}FlexCell: View {
929
929
  let item: WidgetListItem
930
+ let cellRatio: Double
930
931
  let defaultTitleColor: Color
931
932
  let defaultDescColor: Color
932
933
  let defaultAccentColor: Color
933
934
 
934
- // Determine display mode based on item data:
935
- // - hasRightIconOrDesc: show as list row (horizontal)
936
- // - hasTitle: show as card (icon top, title bottom)
937
- // - else: icon only
938
- private var isListMode: Bool {
939
- item.rightIcon != nil || item.description != nil
935
+ // Display mode based on cell ratio:
936
+ // - ratio == 1.0: list row style (icon left, text right)
937
+ // - ratio < 1.0: card style (icon top, text bottom)
938
+ private var isFullWidth: Bool {
939
+ cellRatio >= 1.0
940
940
  }
941
941
 
942
942
  private var isIconOnly: Bool {
943
- item.title.text.isEmpty && !isListMode
943
+ item.title.text.isEmpty
944
944
  }
945
945
 
946
946
  var body: some View {
947
- if isListMode {
947
+ if isFullWidth {
948
948
  // List row style: [icon] [title + desc] [rightIcon]
949
949
  HStack(spacing: 8) {
950
950
  if let icon = item.icon {
@@ -992,21 +992,18 @@ struct ${w.name}FlexCell: View {
992
992
  WidgetIconView(config: icon, accentColor: defaultAccentColor)
993
993
  }
994
994
  } else {
995
- // Card style: icon on top, title below
995
+ // Card style: icon on top, small title below
996
996
  VStack(spacing: 4) {
997
997
  if let icon = item.icon {
998
998
  WidgetIconView(config: icon, accentColor: defaultAccentColor)
999
999
  }
1000
1000
 
1001
1001
  Text(item.title.text)
1002
- .font(item.title.resolvedFont(default: .caption))
1003
- .fontWeight(item.title.resolvedWeight(default: .medium))
1004
- .foregroundColor(
1005
- item.title.color != nil
1006
- ? Color(hex: item.title.color!)
1007
- : defaultTitleColor
1008
- )
1009
- .lineLimit(1)
1002
+ .font(.caption2)
1003
+ .fontWeight(.medium)
1004
+ .foregroundColor(defaultTitleColor)
1005
+ .lineLimit(2)
1006
+ .multilineTextAlignment(.center)
1010
1007
  .truncationMode(.tail)
1011
1008
  }
1012
1009
  .frame(maxWidth: .infinity, maxHeight: .infinity)
@@ -1179,6 +1176,7 @@ struct ${w.name}FlexRow: View {
1179
1176
  @ViewBuilder
1180
1177
  private func cellView(colIndex: Int, width: CGFloat) -> some View {
1181
1178
  let index = getItemIndex(rowIndex: rowIndex, colIndex: colIndex)
1179
+ let cellRatio = rowRatios[colIndex]
1182
1180
  let verticalAlignment: Alignment = {
1183
1181
  switch alignItems {
1184
1182
  case .flexStart: return .top
@@ -1193,6 +1191,7 @@ struct ${w.name}FlexRow: View {
1193
1191
  Link(destination: url) {
1194
1192
  ${w.name}FlexCell(
1195
1193
  item: item,
1194
+ cellRatio: cellRatio,
1196
1195
  defaultTitleColor: titleColor,
1197
1196
  defaultDescColor: subtitleColor,
1198
1197
  defaultAccentColor: accentColor
@@ -1202,6 +1201,7 @@ struct ${w.name}FlexRow: View {
1202
1201
  } else {
1203
1202
  ${w.name}FlexCell(
1204
1203
  item: item,
1204
+ cellRatio: cellRatio,
1205
1205
  defaultTitleColor: titleColor,
1206
1206
  defaultDescColor: subtitleColor,
1207
1207
  defaultAccentColor: accentColor