nn-widgets 0.1.0

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,113 @@
1
+ /**
2
+ * Widget data that can be shared between app and widget (legacy flat format)
3
+ */
4
+ export interface WidgetData {
5
+ [key: string]: string | number | boolean | null;
6
+ }
7
+ /**
8
+ * Configuration for updating widget
9
+ */
10
+ export interface WidgetUpdateConfig {
11
+ /**
12
+ * Widget kind identifier (iOS) or widget class name (Android)
13
+ */
14
+ widgetName?: string;
15
+ }
16
+ /**
17
+ * System font size keys (maps to SwiftUI Font / Android sp).
18
+ */
19
+ export type WidgetFontSize = "caption2" | "caption" | "footnote" | "subheadline" | "body" | "headline" | "title3" | "title2" | "title";
20
+ /**
21
+ * Font weight keys.
22
+ */
23
+ export type WidgetFontWeight = "regular" | "medium" | "semibold" | "bold";
24
+ /**
25
+ * Icon configuration for a widget item.
26
+ * - string: SF Symbol name (iOS) or drawable resource name (Android)
27
+ * - object: icon with extra styling
28
+ */
29
+ export type WidgetItemIcon = string | {
30
+ /** Icon name: SF Symbol (iOS), drawable name (Android), or bundled icon name */
31
+ url: string;
32
+ /** Icon size in points/dp @default 32 */
33
+ size?: number;
34
+ /** Border radius (0 = square, size/2 = circle) @default 0 */
35
+ radius?: number;
36
+ /** Background color behind the icon (hex) */
37
+ backgroundColor?: string;
38
+ };
39
+ /**
40
+ * Text configuration for a widget item.
41
+ * - string: plain text
42
+ * - object: text with custom styling
43
+ */
44
+ export type WidgetItemText = string | {
45
+ /** The text content */
46
+ text: string;
47
+ /** Text color (hex). Falls back to widget-level style. */
48
+ color?: string;
49
+ /** Font size key */
50
+ fontSize?: WidgetFontSize;
51
+ /** Font weight */
52
+ fontWeight?: WidgetFontWeight;
53
+ };
54
+ /**
55
+ * A single item in a list-type widget.
56
+ */
57
+ export interface WidgetListItem {
58
+ /** Left icon (SF Symbol name, bundled icon, or object with styling) */
59
+ icon?: WidgetItemIcon;
60
+ /** Right icon (SF Symbol name, bundled icon, or object with styling) */
61
+ rightIcon?: WidgetItemIcon;
62
+ /** Title text (string or styled object) */
63
+ title: WidgetItemText;
64
+ /** Description text (string or styled object) */
65
+ description?: WidgetItemText;
66
+ /** Per-item deep link URL */
67
+ deepLink?: string;
68
+ }
69
+ /**
70
+ * Structured data payload for `updateWidget()`.
71
+ */
72
+ export interface WidgetDataPayload {
73
+ /** Title text (for single/list fallback) */
74
+ title?: string;
75
+ /** Subtitle text (for single/list fallback) */
76
+ subtitle?: string;
77
+ /** Numeric value (for single-type widgets) */
78
+ value?: number;
79
+ /** List items (for list-type widgets) */
80
+ items?: WidgetListItem[];
81
+ }
82
+ /**
83
+ * Widget module interface
84
+ */
85
+ export interface NNWidgetsModuleType {
86
+ /**
87
+ * Set shared data that widgets can read (legacy flat key-value format).
88
+ * Keys are stored with `widget_` prefix in UserDefaults/SharedPreferences.
89
+ */
90
+ setWidgetData(data: WidgetData): Promise<boolean>;
91
+ /**
92
+ * Get current shared widget data (legacy format)
93
+ */
94
+ getWidgetData(): Promise<WidgetData>;
95
+ /**
96
+ * Update a specific widget with structured data.
97
+ * Automatically serializes items to JSON and reloads the widget timeline.
98
+ *
99
+ * @param widgetName - The widget kind identifier (matches `name` in app.json config)
100
+ * @param data - Structured widget data payload
101
+ * @returns Promise<boolean> - Success status
102
+ */
103
+ updateWidget(widgetName: string, data: WidgetDataPayload): Promise<boolean>;
104
+ /**
105
+ * Request widget to reload/update its timeline (iOS) or update (Android)
106
+ */
107
+ reloadWidget(config?: WidgetUpdateConfig): Promise<boolean>;
108
+ /**
109
+ * Check if widgets are supported on this device
110
+ */
111
+ isSupported(): boolean;
112
+ }
113
+ //# sourceMappingURL=NNWidgets.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NNWidgets.types.d.ts","sourceRoot":"","sources":["../src/NNWidgets.types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,UAAU,GACV,SAAS,GACT,UAAU,GACV,aAAa,GACb,MAAM,GACN,UAAU,GACV,QAAQ,GACR,QAAQ,GACR,OAAO,CAAC;AAEZ;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IACE,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEN;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IACE,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,kBAAkB;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,wEAAwE;IACxE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,cAAc,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElD;;OAEG;IACH,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAErC;;;;;;;OAOG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5E;;OAEG;IACH,YAAY,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5D;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC;CACxB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=NNWidgets.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NNWidgets.types.js","sourceRoot":"","sources":["../src/NNWidgets.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Widget data that can be shared between app and widget (legacy flat format)\n */\nexport interface WidgetData {\n [key: string]: string | number | boolean | null;\n}\n\n/**\n * Configuration for updating widget\n */\nexport interface WidgetUpdateConfig {\n /**\n * Widget kind identifier (iOS) or widget class name (Android)\n */\n widgetName?: string;\n}\n\n// ──────────────────────────────────────────────\n// Rich widget data types (for list/single widgets)\n// ──────────────────────────────────────────────\n\n/**\n * System font size keys (maps to SwiftUI Font / Android sp).\n */\nexport type WidgetFontSize =\n | \"caption2\"\n | \"caption\"\n | \"footnote\"\n | \"subheadline\"\n | \"body\"\n | \"headline\"\n | \"title3\"\n | \"title2\"\n | \"title\";\n\n/**\n * Font weight keys.\n */\nexport type WidgetFontWeight = \"regular\" | \"medium\" | \"semibold\" | \"bold\";\n\n/**\n * Icon configuration for a widget item.\n * - string: SF Symbol name (iOS) or drawable resource name (Android)\n * - object: icon with extra styling\n */\nexport type WidgetItemIcon =\n | string\n | {\n /** Icon name: SF Symbol (iOS), drawable name (Android), or bundled icon name */\n url: string;\n /** Icon size in points/dp @default 32 */\n size?: number;\n /** Border radius (0 = square, size/2 = circle) @default 0 */\n radius?: number;\n /** Background color behind the icon (hex) */\n backgroundColor?: string;\n };\n\n/**\n * Text configuration for a widget item.\n * - string: plain text\n * - object: text with custom styling\n */\nexport type WidgetItemText =\n | string\n | {\n /** The text content */\n text: string;\n /** Text color (hex). Falls back to widget-level style. */\n color?: string;\n /** Font size key */\n fontSize?: WidgetFontSize;\n /** Font weight */\n fontWeight?: WidgetFontWeight;\n };\n\n/**\n * A single item in a list-type widget.\n */\nexport interface WidgetListItem {\n /** Left icon (SF Symbol name, bundled icon, or object with styling) */\n icon?: WidgetItemIcon;\n /** Right icon (SF Symbol name, bundled icon, or object with styling) */\n rightIcon?: WidgetItemIcon;\n /** Title text (string or styled object) */\n title: WidgetItemText;\n /** Description text (string or styled object) */\n description?: WidgetItemText;\n /** Per-item deep link URL */\n deepLink?: string;\n}\n\n/**\n * Structured data payload for `updateWidget()`.\n */\nexport interface WidgetDataPayload {\n /** Title text (for single/list fallback) */\n title?: string;\n /** Subtitle text (for single/list fallback) */\n subtitle?: string;\n /** Numeric value (for single-type widgets) */\n value?: number;\n /** List items (for list-type widgets) */\n items?: WidgetListItem[];\n}\n\n/**\n * Widget module interface\n */\nexport interface NNWidgetsModuleType {\n /**\n * Set shared data that widgets can read (legacy flat key-value format).\n * Keys are stored with `widget_` prefix in UserDefaults/SharedPreferences.\n */\n setWidgetData(data: WidgetData): Promise<boolean>;\n\n /**\n * Get current shared widget data (legacy format)\n */\n getWidgetData(): Promise<WidgetData>;\n\n /**\n * Update a specific widget with structured data.\n * Automatically serializes items to JSON and reloads the widget timeline.\n *\n * @param widgetName - The widget kind identifier (matches `name` in app.json config)\n * @param data - Structured widget data payload\n * @returns Promise<boolean> - Success status\n */\n updateWidget(widgetName: string, data: WidgetDataPayload): Promise<boolean>;\n\n /**\n * Request widget to reload/update its timeline (iOS) or update (Android)\n */\n reloadWidget(config?: WidgetUpdateConfig): Promise<boolean>;\n\n /**\n * Check if widgets are supported on this device\n */\n isSupported(): boolean;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+ export default _default;
3
+ //# sourceMappingURL=NNWidgetsModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NNWidgetsModule.d.ts","sourceRoot":"","sources":["../src/NNWidgetsModule.ts"],"names":[],"mappings":";AACA,wBAAgD"}
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from "expo-modules-core";
2
+ export default requireNativeModule("NNWidgets");
3
+ //# sourceMappingURL=NNWidgetsModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NNWidgetsModule.js","sourceRoot":"","sources":["../src/NNWidgetsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,eAAe,mBAAmB,CAAC,WAAW,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from \"expo-modules-core\";\nexport default requireNativeModule(\"NNWidgets\");\n"]}
@@ -0,0 +1,11 @@
1
+ import type { WidgetData, WidgetDataPayload, WidgetListItem, WidgetItemIcon, WidgetItemText, WidgetUpdateConfig, NNWidgetsModuleType } from "./NNWidgets.types";
2
+ export type { WidgetData, WidgetDataPayload, WidgetListItem, WidgetItemIcon, WidgetItemText, WidgetUpdateConfig, NNWidgetsModuleType, };
3
+ /**
4
+ * NNWidgets - Native widget integration for React Native / Expo
5
+ *
6
+ * This module provides APIs to communicate between your React Native app
7
+ * and native widgets (iOS WidgetKit & Android App Widgets).
8
+ */
9
+ export declare const NNWidgets: NNWidgetsModuleType;
10
+ export default NNWidgets;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,mBAAmB,GACpB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,mBAqJvB,CAAC;AAEF,eAAe,SAAS,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,152 @@
1
+ import NNWidgetsModule from "./NNWidgetsModule";
2
+ /**
3
+ * NNWidgets - Native widget integration for React Native / Expo
4
+ *
5
+ * This module provides APIs to communicate between your React Native app
6
+ * and native widgets (iOS WidgetKit & Android App Widgets).
7
+ */
8
+ export const NNWidgets = {
9
+ /**
10
+ * Set shared data that widgets can read (legacy flat key-value format).
11
+ * Data is stored in App Groups (iOS) or SharedPreferences (Android).
12
+ *
13
+ * @param data - Key-value pairs to share with widget
14
+ * @returns Promise<boolean> - Success status
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * await NNWidgets.setWidgetData({
19
+ * userName: 'John',
20
+ * points: 100,
21
+ * isLoggedIn: true
22
+ * });
23
+ * ```
24
+ */
25
+ async setWidgetData(data) {
26
+ try {
27
+ return await NNWidgetsModule.setWidgetData(data);
28
+ }
29
+ catch (error) {
30
+ console.error("[NNWidgets] Failed to set widget data:", error);
31
+ return false;
32
+ }
33
+ },
34
+ /**
35
+ * Get current shared widget data.
36
+ *
37
+ * @returns Promise<WidgetData> - Current shared data
38
+ */
39
+ async getWidgetData() {
40
+ try {
41
+ return await NNWidgetsModule.getWidgetData();
42
+ }
43
+ catch (error) {
44
+ console.error("[NNWidgets] Failed to get widget data:", error);
45
+ return {};
46
+ }
47
+ },
48
+ /**
49
+ * Update a specific widget with structured data.
50
+ * Automatically serializes items to JSON, stores data with proper key prefixes,
51
+ * and reloads the widget timeline.
52
+ *
53
+ * @param widgetName - The widget kind identifier (matches `name` in app.json widgets config)
54
+ * @param data - Structured widget data payload
55
+ * @returns Promise<boolean> - Success status
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * // Update a list-type widget
60
+ * await NNWidgets.updateWidget('SessionsWidget', {
61
+ * items: [
62
+ * {
63
+ * icon: 'brainstorm',
64
+ * title: { text: 'Morning Focus', color: '#1E1E2E', fontWeight: 'bold' },
65
+ * description: '25 min session',
66
+ * deepLink: 'myapp://session/123',
67
+ * },
68
+ * {
69
+ * icon: { url: 'meditation', size: 28, radius: 14 },
70
+ * title: 'Evening Wind Down',
71
+ * description: { text: '15 min', color: '#888888' },
72
+ * },
73
+ * ],
74
+ * });
75
+ *
76
+ * // Update a single-type widget
77
+ * await NNWidgets.updateWidget('StatsWidget', {
78
+ * title: 'Focus Time',
79
+ * subtitle: 'This week',
80
+ * value: 142,
81
+ * });
82
+ * ```
83
+ */
84
+ async updateWidget(widgetName, data) {
85
+ try {
86
+ const flatData = {};
87
+ if (data.title !== undefined) {
88
+ flatData[`${widgetName}_title`] = data.title;
89
+ }
90
+ if (data.subtitle !== undefined) {
91
+ flatData[`${widgetName}_subtitle`] = data.subtitle;
92
+ }
93
+ if (data.value !== undefined) {
94
+ flatData[`${widgetName}_value`] = data.value;
95
+ }
96
+ if (data.items !== undefined) {
97
+ // Serialize items array to JSON string for native storage
98
+ flatData[`${widgetName}_items`] = JSON.stringify(data.items);
99
+ }
100
+ const success = await NNWidgetsModule.setWidgetData(flatData);
101
+ if (success) {
102
+ await NNWidgetsModule.reloadWidget(widgetName);
103
+ }
104
+ return success;
105
+ }
106
+ catch (error) {
107
+ console.error(`[NNWidgets] Failed to update widget "${widgetName}":`, error);
108
+ return false;
109
+ }
110
+ },
111
+ /**
112
+ * Request widget to reload/update.
113
+ * - iOS: Reloads widget timeline
114
+ * - Android: Triggers widget update broadcast
115
+ *
116
+ * @param config - Optional configuration
117
+ * @returns Promise<boolean> - Success status
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * // Update all widgets
122
+ * await NNWidgets.reloadWidget();
123
+ *
124
+ * // Update specific widget
125
+ * await NNWidgets.reloadWidget({ widgetName: 'MyWidget' });
126
+ * ```
127
+ */
128
+ async reloadWidget(config) {
129
+ try {
130
+ return await NNWidgetsModule.reloadWidget(config?.widgetName || null);
131
+ }
132
+ catch (error) {
133
+ console.error("[NNWidgets] Failed to reload widget:", error);
134
+ return false;
135
+ }
136
+ },
137
+ /**
138
+ * Check if widgets are supported on this device.
139
+ *
140
+ * @returns boolean - True if widgets are supported
141
+ */
142
+ isSupported() {
143
+ try {
144
+ return NNWidgetsModule.isSupported();
145
+ }
146
+ catch (error) {
147
+ return false;
148
+ }
149
+ },
150
+ };
151
+ export default NNWidgets;
152
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAqBhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAwB;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,aAAa,CAAC,IAAgB;QAClC,IAAI,CAAC;YACH,OAAO,MAAM,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YAC/D,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,aAAa;QACjB,IAAI,CAAC;YACH,OAAO,MAAM,eAAe,CAAC,aAAa,EAAE,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YAC/D,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,KAAK,CAAC,YAAY,CAChB,UAAkB,EAClB,IAAuB;QAEvB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAe,EAAE,CAAC;YAEhC,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,QAAQ,CAAC,GAAG,UAAU,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YAC/C,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAChC,QAAQ,CAAC,GAAG,UAAU,WAAW,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;YACrD,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,QAAQ,CAAC,GAAG,UAAU,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YAC/C,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,0DAA0D;gBAC1D,QAAQ,CAAC,GAAG,UAAU,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC/D,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC9D,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,eAAe,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;YACjD,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,wCAAwC,UAAU,IAAI,EACtD,KAAK,CACN,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,YAAY,CAAC,MAA2B;QAC5C,IAAI,CAAC;YACH,OAAO,MAAM,eAAe,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,IAAI,IAAI,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC,CAAC;YAC7D,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,IAAI,CAAC;YACH,OAAO,eAAe,CAAC,WAAW,EAAE,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF,CAAC;AAEF,eAAe,SAAS,CAAC","sourcesContent":["import NNWidgetsModule from \"./NNWidgetsModule\";\nimport type {\n WidgetData,\n WidgetDataPayload,\n WidgetListItem,\n WidgetItemIcon,\n WidgetItemText,\n WidgetUpdateConfig,\n NNWidgetsModuleType,\n} from \"./NNWidgets.types\";\n\nexport type {\n WidgetData,\n WidgetDataPayload,\n WidgetListItem,\n WidgetItemIcon,\n WidgetItemText,\n WidgetUpdateConfig,\n NNWidgetsModuleType,\n};\n\n/**\n * NNWidgets - Native widget integration for React Native / Expo\n *\n * This module provides APIs to communicate between your React Native app\n * and native widgets (iOS WidgetKit & Android App Widgets).\n */\nexport const NNWidgets: NNWidgetsModuleType = {\n /**\n * Set shared data that widgets can read (legacy flat key-value format).\n * Data is stored in App Groups (iOS) or SharedPreferences (Android).\n *\n * @param data - Key-value pairs to share with widget\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * await NNWidgets.setWidgetData({\n * userName: 'John',\n * points: 100,\n * isLoggedIn: true\n * });\n * ```\n */\n async setWidgetData(data: WidgetData): Promise<boolean> {\n try {\n return await NNWidgetsModule.setWidgetData(data);\n } catch (error) {\n console.error(\"[NNWidgets] Failed to set widget data:\", error);\n return false;\n }\n },\n\n /**\n * Get current shared widget data.\n *\n * @returns Promise<WidgetData> - Current shared data\n */\n async getWidgetData(): Promise<WidgetData> {\n try {\n return await NNWidgetsModule.getWidgetData();\n } catch (error) {\n console.error(\"[NNWidgets] Failed to get widget data:\", error);\n return {};\n }\n },\n\n /**\n * Update a specific widget with structured data.\n * Automatically serializes items to JSON, stores data with proper key prefixes,\n * and reloads the widget timeline.\n *\n * @param widgetName - The widget kind identifier (matches `name` in app.json widgets config)\n * @param data - Structured widget data payload\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * // Update a list-type widget\n * await NNWidgets.updateWidget('SessionsWidget', {\n * items: [\n * {\n * icon: 'brainstorm',\n * title: { text: 'Morning Focus', color: '#1E1E2E', fontWeight: 'bold' },\n * description: '25 min session',\n * deepLink: 'myapp://session/123',\n * },\n * {\n * icon: { url: 'meditation', size: 28, radius: 14 },\n * title: 'Evening Wind Down',\n * description: { text: '15 min', color: '#888888' },\n * },\n * ],\n * });\n *\n * // Update a single-type widget\n * await NNWidgets.updateWidget('StatsWidget', {\n * title: 'Focus Time',\n * subtitle: 'This week',\n * value: 142,\n * });\n * ```\n */\n async updateWidget(\n widgetName: string,\n data: WidgetDataPayload,\n ): Promise<boolean> {\n try {\n const flatData: WidgetData = {};\n\n if (data.title !== undefined) {\n flatData[`${widgetName}_title`] = data.title;\n }\n if (data.subtitle !== undefined) {\n flatData[`${widgetName}_subtitle`] = data.subtitle;\n }\n if (data.value !== undefined) {\n flatData[`${widgetName}_value`] = data.value;\n }\n if (data.items !== undefined) {\n // Serialize items array to JSON string for native storage\n flatData[`${widgetName}_items`] = JSON.stringify(data.items);\n }\n\n const success = await NNWidgetsModule.setWidgetData(flatData);\n if (success) {\n await NNWidgetsModule.reloadWidget(widgetName);\n }\n return success;\n } catch (error) {\n console.error(\n `[NNWidgets] Failed to update widget \"${widgetName}\":`,\n error,\n );\n return false;\n }\n },\n\n /**\n * Request widget to reload/update.\n * - iOS: Reloads widget timeline\n * - Android: Triggers widget update broadcast\n *\n * @param config - Optional configuration\n * @returns Promise<boolean> - Success status\n *\n * @example\n * ```ts\n * // Update all widgets\n * await NNWidgets.reloadWidget();\n *\n * // Update specific widget\n * await NNWidgets.reloadWidget({ widgetName: 'MyWidget' });\n * ```\n */\n async reloadWidget(config?: WidgetUpdateConfig): Promise<boolean> {\n try {\n return await NNWidgetsModule.reloadWidget(config?.widgetName || null);\n } catch (error) {\n console.error(\"[NNWidgets] Failed to reload widget:\", error);\n return false;\n }\n },\n\n /**\n * Check if widgets are supported on this device.\n *\n * @returns boolean - True if widgets are supported\n */\n isSupported(): boolean {\n try {\n return NNWidgetsModule.isSupported();\n } catch (error) {\n return false;\n }\n },\n};\n\nexport default NNWidgets;\n"]}
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["ios", "android"],
3
+ "ios": {
4
+ "modules": ["NNWidgetsModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.nnwidgets.NNWidgetsModule"]
8
+ }
9
+ }
@@ -0,0 +1,27 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'NNWidgets'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = { :ios => '15.1' }
14
+ s.swift_version = '5.9'
15
+ s.source = { git: 'https://github.com/NamReplIT/expo-utils' }
16
+ s.static_framework = true
17
+
18
+ s.dependency 'ExpoModulesCore'
19
+
20
+ # Swift/Objective-C compatibility
21
+ s.pod_target_xcconfig = {
22
+ 'DEFINES_MODULE' => 'YES',
23
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule'
24
+ }
25
+
26
+ s.source_files = "**/*.{h,m,swift}"
27
+ end
@@ -0,0 +1,97 @@
1
+ import ExpoModulesCore
2
+ import WidgetKit
3
+
4
+ public class NNWidgetsModule: Module {
5
+
6
+ // App Group identifier - will be set via plugin config
7
+ private var appGroupIdentifier: String {
8
+ // Try to get from Info.plist or use default pattern
9
+ if let identifier = Bundle.main.object(forInfoDictionaryKey: "NNWidgetsAppGroup") as? String {
10
+ return identifier
11
+ }
12
+ // Fallback: derive from bundle identifier
13
+ if let bundleId = Bundle.main.bundleIdentifier {
14
+ return "group.\(bundleId)"
15
+ }
16
+ return "group.app.widget"
17
+ }
18
+
19
+ private var userDefaults: UserDefaults? {
20
+ return UserDefaults(suiteName: appGroupIdentifier)
21
+ }
22
+
23
+ public func definition() -> ModuleDefinition {
24
+ Name("NNWidgets")
25
+
26
+ // Check if widgets are supported (iOS 14+)
27
+ Function("isSupported") { () -> Bool in
28
+ if #available(iOS 14.0, *) {
29
+ return true
30
+ }
31
+ return false
32
+ }
33
+
34
+ // Set widget data to shared UserDefaults
35
+ AsyncFunction("setWidgetData") { (data: [String: Any]) -> Bool in
36
+ guard let defaults = self.userDefaults else {
37
+ print("[NNWidgets] Failed to access App Group UserDefaults")
38
+ return false
39
+ }
40
+
41
+ // Store each key-value pair
42
+ for (key, value) in data {
43
+ defaults.set(value, forKey: "widget_\(key)")
44
+ }
45
+
46
+ // Store keys list for retrieval
47
+ let keys = data.keys.map { "widget_\($0)" }
48
+ defaults.set(keys, forKey: "widget_data_keys")
49
+
50
+ defaults.synchronize()
51
+ print("[NNWidgets] Widget data saved: \(data.keys.joined(separator: ", "))")
52
+ return true
53
+ }
54
+
55
+ // Get widget data from shared UserDefaults
56
+ AsyncFunction("getWidgetData") { () -> [String: Any] in
57
+ guard let defaults = self.userDefaults else {
58
+ print("[NNWidgets] Failed to access App Group UserDefaults")
59
+ return [:]
60
+ }
61
+
62
+ guard let keys = defaults.array(forKey: "widget_data_keys") as? [String] else {
63
+ return [:]
64
+ }
65
+
66
+ var result: [String: Any] = [:]
67
+ for key in keys {
68
+ if let value = defaults.object(forKey: key) {
69
+ // Remove "widget_" prefix from key
70
+ let cleanKey = String(key.dropFirst(7))
71
+ result[cleanKey] = value
72
+ }
73
+ }
74
+
75
+ return result
76
+ }
77
+
78
+ // Reload widget timeline
79
+ AsyncFunction("reloadWidget") { (widgetKind: String?) -> Bool in
80
+ if #available(iOS 14.0, *) {
81
+ DispatchQueue.main.async {
82
+ if let kind = widgetKind {
83
+ // Reload specific widget
84
+ WidgetCenter.shared.reloadTimelines(ofKind: kind)
85
+ print("[NNWidgets] Reloaded widget: \(kind)")
86
+ } else {
87
+ // Reload all widgets
88
+ WidgetCenter.shared.reloadAllTimelines()
89
+ print("[NNWidgets] Reloaded all widgets")
90
+ }
91
+ }
92
+ return true
93
+ }
94
+ return false
95
+ }
96
+ }
97
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "nn-widgets",
3
+ "version": "0.1.0",
4
+ "description": "Expo config plugin for adding native widgets (iOS WidgetKit & Android App Widgets)",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build && npm run build:plugin",
9
+ "build:plugin": "npx tsc -p plugin/tsconfig.json",
10
+ "clean": "expo-module clean && rm -rf plugin/build",
11
+ "lint": "expo-module lint",
12
+ "test": "expo-module test",
13
+ "prepare": "expo-module prepare",
14
+ "prepublishOnly": "expo-module prepublishOnly",
15
+ "expo-module": "expo-module"
16
+ },
17
+ "keywords": [
18
+ "react-native",
19
+ "expo",
20
+ "expo-plugin",
21
+ "widget",
22
+ "ios-widget",
23
+ "android-widget",
24
+ "widgetkit",
25
+ "app-widget"
26
+ ],
27
+ "repository": "https://github.com/NamReplIT/expo-utils",
28
+ "bugs": {
29
+ "url": "https://github.com/NamReplIT/expo-utils/issues"
30
+ },
31
+ "author": "NamReplIT <89992703+NamReplIT@users.noreply.github.com>",
32
+ "license": "MIT",
33
+ "homepage": "https://github.com/NamReplIT/expo-utils#readme",
34
+ "dependencies": {},
35
+ "devDependencies": {
36
+ "@expo/config-plugins": "^9.0.0",
37
+ "@types/node": "^20.0.0",
38
+ "expo-module-scripts": "^4.0.5",
39
+ "expo-modules-core": "^2.5.0",
40
+ "typescript": "^5.0.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@types/react": "*",
44
+ "@types/react-native": "*",
45
+ "expo": "*",
46
+ "react": "*",
47
+ "react-native": "*"
48
+ }
49
+ }
@@ -0,0 +1,9 @@
1
+ import { ConfigPlugin } from "@expo/config-plugins";
2
+ import { withIosWidget } from "./withIosWidget";
3
+ import { withAndroidWidget } from "./withAndroidWidget";
4
+ import type { NNWidgetsPluginProps, WidgetConfig, ResolvedPluginProps, ResolvedWidgetConfig } from "./types";
5
+ export type { NNWidgetsPluginProps, WidgetConfig, ResolvedPluginProps, ResolvedWidgetConfig, };
6
+ export { withIosWidget, withAndroidWidget };
7
+ declare const _default: ConfigPlugin<void | NNWidgetsPluginProps>;
8
+ export default _default;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,GACrB,CAAC;AACF,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,CAAC;;AAyE5C,wBAAyE"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withAndroidWidget = exports.withIosWidget = void 0;
4
+ const config_plugins_1 = require("@expo/config-plugins");
5
+ const withIosWidget_1 = require("./withIosWidget");
6
+ Object.defineProperty(exports, "withIosWidget", { enumerable: true, get: function () { return withIosWidget_1.withIosWidget; } });
7
+ const withAndroidWidget_1 = require("./withAndroidWidget");
8
+ Object.defineProperty(exports, "withAndroidWidget", { enumerable: true, get: function () { return withAndroidWidget_1.withAndroidWidget; } });
9
+ /**
10
+ * nn-widgets Expo Config Plugin
11
+ *
12
+ * Adds native widget support for iOS (WidgetKit) and Android (App Widgets)
13
+ * to your Expo/React Native app.
14
+ *
15
+ * Supports multiple independent widgets per app.
16
+ *
17
+ * @example
18
+ * ```js
19
+ * // app.json or app.config.js
20
+ * {
21
+ * "expo": {
22
+ * "plugins": [
23
+ * ["nn-widgets", {
24
+ * "widgets": [
25
+ * {
26
+ * "name": "MainWidget",
27
+ * "displayName": "My App",
28
+ * "description": "Quick access",
29
+ * "deepLinkUrl": "myapp://widget/home",
30
+ * "widgetFamilies": ["systemSmall", "systemMedium"]
31
+ * },
32
+ * {
33
+ * "name": "StatsWidget",
34
+ * "displayName": "Stats",
35
+ * "description": "View your stats",
36
+ * "deepLinkUrl": "myapp://widget/stats",
37
+ * "widgetFamilies": ["systemSmall"]
38
+ * }
39
+ * ],
40
+ * "ios": {
41
+ * "deploymentTarget": "17.0",
42
+ * "devTeam": "YOUR_TEAM_ID"
43
+ * },
44
+ * "android": {
45
+ * "minWidth": 110,
46
+ * "minHeight": 40
47
+ * }
48
+ * }]
49
+ * ]
50
+ * }
51
+ * }
52
+ * ```
53
+ */
54
+ const withNNWidgets = (config, props = {}) => {
55
+ const pluginProps = props || {};
56
+ const widgetCount = pluginProps.widgets?.length || 1;
57
+ console.log(`[nn-widgets] Configuring ${widgetCount} native widget(s)...`);
58
+ // Apply iOS widget configuration
59
+ config = (0, withIosWidget_1.withIosWidget)(config, pluginProps);
60
+ // Apply Android widget configuration
61
+ config = (0, withAndroidWidget_1.withAndroidWidget)(config, pluginProps);
62
+ console.log("[nn-widgets] Widget configuration complete!");
63
+ return config;
64
+ };
65
+ // Package info for createRunOncePlugin
66
+ const pkg = {
67
+ name: "nn-widgets",
68
+ version: "0.2.0",
69
+ };
70
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(withNNWidgets, pkg.name, pkg.version);