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
|
+
}
|
package/expo-module.config.json
CHANGED
package/package.json
CHANGED
|
@@ -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
|
-
//
|
|
935
|
-
// -
|
|
936
|
-
// -
|
|
937
|
-
|
|
938
|
-
|
|
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
|
|
943
|
+
item.title.text.isEmpty
|
|
944
944
|
}
|
|
945
945
|
|
|
946
946
|
var body: some View {
|
|
947
|
-
if
|
|
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(
|
|
1003
|
-
.fontWeight(
|
|
1004
|
-
.foregroundColor(
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|