nn-widgets 0.1.3 → 0.1.5
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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withIosWidget.d.ts","sourceRoot":"","sources":["../src/withIosWidget.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"withIosWidget.d.ts","sourceRoot":"","sources":["../src/withIosWidget.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAIb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,SAAS,CAAC;AA8mDjB,eAAO,MAAM,aAAa,EAAE,YAAY,CAAC,oBAAoB,CAugB5D,CAAC;AAMF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GACvB,mBAAmB,CA2GrB;AAED,eAAe,aAAa,CAAC"}
|
|
@@ -1044,11 +1044,10 @@ struct ${w.name}EntryView: View {
|
|
|
1044
1044
|
.background(${bgColor})
|
|
1045
1045
|
} else {
|
|
1046
1046
|
GeometryReader { geometry in
|
|
1047
|
-
let
|
|
1048
|
-
let
|
|
1049
|
-
let availableHeight = geometry.size.height - (padding * 2)
|
|
1047
|
+
let availableWidth = geometry.size.width
|
|
1048
|
+
let availableHeight = geometry.size.height
|
|
1050
1049
|
|
|
1051
|
-
VStack(spacing:
|
|
1050
|
+
VStack(spacing: 6) {
|
|
1052
1051
|
let layout = entry.layout
|
|
1053
1052
|
let totalSlots = layout.map { $0.ratios.count }.reduce(0, +)
|
|
1054
1053
|
let items = Array(entry.items.prefix(totalSlots))
|
|
@@ -1056,7 +1055,7 @@ struct ${w.name}EntryView: View {
|
|
|
1056
1055
|
ForEach(0..<layout.count, id: \\.self) { rowIndex in
|
|
1057
1056
|
let rowConfig = layout[rowIndex]
|
|
1058
1057
|
let rowRatios = rowConfig.ratios
|
|
1059
|
-
let rowHeight = (availableHeight - CGFloat(layout.count - 1) *
|
|
1058
|
+
let rowHeight = (availableHeight - CGFloat(layout.count - 1) * 6) / CGFloat(layout.count)
|
|
1060
1059
|
|
|
1061
1060
|
${w.name}FlexRow(
|
|
1062
1061
|
rowRatios: rowRatios,
|
|
@@ -1073,7 +1072,6 @@ struct ${w.name}EntryView: View {
|
|
|
1073
1072
|
)
|
|
1074
1073
|
}
|
|
1075
1074
|
}
|
|
1076
|
-
.padding(padding)
|
|
1077
1075
|
}
|
|
1078
1076
|
.background(${bgColor})
|
|
1079
1077
|
}
|
|
@@ -1105,7 +1103,7 @@ struct ${w.name}FlexRow: View {
|
|
|
1105
1103
|
|
|
1106
1104
|
var body: some View {
|
|
1107
1105
|
let itemCount = rowRatios.count
|
|
1108
|
-
let defaultSpacing: CGFloat =
|
|
1106
|
+
let defaultSpacing: CGFloat = 6
|
|
1109
1107
|
|
|
1110
1108
|
// For space-* modes, scale down cells to leave room for visible spacing
|
|
1111
1109
|
// Use 60% of width for content, 40% for spacing distribution
|
|
@@ -1228,7 +1226,6 @@ struct ${w.name}: Widget {
|
|
|
1228
1226
|
.widgetURL(deepLinkUrl)
|
|
1229
1227
|
} else {
|
|
1230
1228
|
${w.name}EntryView(entry: entry)
|
|
1231
|
-
.padding()
|
|
1232
1229
|
.background(${bgColor})
|
|
1233
1230
|
.widgetURL(deepLinkUrl)
|
|
1234
1231
|
}
|
|
@@ -1513,7 +1510,10 @@ extension Color {
|
|
|
1513
1510
|
`
|
|
1514
1511
|
: "";
|
|
1515
1512
|
// Determine if we need UIKit import (for UIImage check in icon rendering)
|
|
1516
|
-
const needsUIKit = props.widgets.some((w) => w.type === "list" ||
|
|
1513
|
+
const needsUIKit = props.widgets.some((w) => w.type === "list" ||
|
|
1514
|
+
w.type === "single" ||
|
|
1515
|
+
w.type === "flex-grid" ||
|
|
1516
|
+
w.type === "grid");
|
|
1517
1517
|
const uiKitImport = needsUIKit ? "\nimport UIKit" : "";
|
|
1518
1518
|
return `//
|
|
1519
1519
|
// Widgets.swift
|