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.
package/README.md ADDED
@@ -0,0 +1,577 @@
1
+ # nn-widgets
2
+
3
+ Expo config plugin for native widgets — iOS WidgetKit & Android App Widgets.
4
+
5
+ Supports four widget types:
6
+
7
+ | Type | Description | Data Source |
8
+ | ---------- | ------------------------------------------------------- | --------------------------- |
9
+ | `"image"` | Full-bleed background image | Static asset at build time |
10
+ | `"single"` | Icon + Title + Subtitle + Value | `updateWidget()` at runtime |
11
+ | `"list"` | Vertical list of items with icon/title/description | `updateWidget()` at runtime |
12
+ | `"grid"` | Grid of items, each cell with icon on top + title below | `updateWidget()` at runtime |
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npx expo install nn-widgets
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### 1. Configure in `app.json`
25
+
26
+ ```json
27
+ {
28
+ "plugins": [
29
+ [
30
+ "nn-widgets",
31
+ {
32
+ "widgets": [
33
+ {
34
+ "name": "QuickActions",
35
+ "displayName": "Quick Actions",
36
+ "type": "list",
37
+ "deepLinkUrl": "myapp://widget",
38
+ "widgetFamilies": ["systemMedium", "systemLarge"],
39
+ "fallbackTitle": "Quick Actions",
40
+ "fallbackSubtitle": "Open app to set up",
41
+ "style": {
42
+ "backgroundColor": "#1E1E2E",
43
+ "titleColor": "#CDD6F4",
44
+ "subtitleColor": "#A6ADC8",
45
+ "accentColor": "#F38BA8"
46
+ }
47
+ }
48
+ ],
49
+ "ios": {
50
+ "devTeam": "YOUR_TEAM_ID",
51
+ "useAppGroups": true
52
+ }
53
+ }
54
+ ]
55
+ ]
56
+ }
57
+ ```
58
+
59
+ ### 2. Push data from your app
60
+
61
+ ```ts
62
+ import { NNWidgets } from "nn-widgets";
63
+
64
+ await NNWidgets.updateWidget("QuickActions", {
65
+ items: [
66
+ {
67
+ icon: "star.fill",
68
+ title: "Morning Focus",
69
+ description: "25 min session",
70
+ deepLink: "myapp://session/123",
71
+ },
72
+ {
73
+ icon: { url: "brain", size: 28, radius: 14, backgroundColor: "#3B3B5C" },
74
+ title: { text: "Brainstorm", color: "#F38BA8", fontWeight: "bold" },
75
+ description: { text: "15 min", fontSize: "caption" },
76
+ },
77
+ ],
78
+ });
79
+ ```
80
+
81
+ ### 3. Rebuild
82
+
83
+ ```bash
84
+ npx expo prebuild --clean
85
+ npx expo run:ios
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Plugin Configuration
91
+
92
+ ### `widgets[]` — Per-widget config
93
+
94
+ | Property | Type | Default | Description |
95
+ | ------------------ | ------------------------------- | ---------------------------------------------- | ------------------------------------------------- |
96
+ | `name` | `string` | **required** | Unique identifier (valid Swift/Kotlin identifier) |
97
+ | `displayName` | `string` | **required** | Name shown in widget gallery |
98
+ | `description` | `string` | `"A widget for your app"` | Description in widget gallery |
99
+ | `type` | `"single" \| "list" \| "image"` | auto-detect | Widget display type |
100
+ | `deepLinkUrl` | `string` | — | URL opened when widget is tapped |
101
+ | `showAppIcon` | `boolean` | `true` | Show app icon in widget UI |
102
+ | `widgetFamilies` | `string[]` | `["systemSmall","systemMedium","systemLarge"]` | Supported sizes (iOS) |
103
+ | `fallbackTitle` | `string` | `displayName` | Title shown when no data set |
104
+ | `fallbackSubtitle` | `string` | `"Open app to start"` | Subtitle shown when no data set |
105
+ | `image` | `object \| string` | — | Background image paths (image type) |
106
+ | `icons` | `Record<string, string>` | — | Bundled icon images (name → path) |
107
+ | `style` | `object` | — | Widget-level colors |
108
+ | `android` | `object` | — | Android-specific overrides |
109
+
110
+ #### `type` auto-detection
111
+
112
+ - If `image` prop is set → `"image"`
113
+ - Otherwise → `"single"`
114
+ - Set `type: "list"` explicitly for list widgets
115
+ - Set `type: "grid"` explicitly for grid widgets
116
+
117
+ #### `gridLayout` (for type `"grid"`)
118
+
119
+ Format: `"COLUMNSxROWS"` — controls the grid dimensions.
120
+
121
+ ```json
122
+ { "gridLayout": "3x2" }
123
+ ```
124
+
125
+ | Value | Columns | Rows | Max Items |
126
+ | ------- | ------- | ---- | --------- |
127
+ | `"2x2"` | 2 | 2 | 4 |
128
+ | `"3x2"` | 3 | 2 | 6 |
129
+ | `"4x2"` | 4 | 2 | 8 |
130
+ | `"2x3"` | 2 | 3 | 6 |
131
+
132
+ If omitted, columns auto-adjust based on widget family:
133
+ - `systemSmall`: 2 columns
134
+ - `systemMedium`: 4 columns
135
+ - `systemLarge`: 4 columns
136
+
137
+ #### `image` (for type `"image"`)
138
+
139
+ ```json
140
+ {
141
+ "image": {
142
+ "small": "./assets/widgets/bg-small.png",
143
+ "medium": "./assets/widgets/bg-medium.png",
144
+ "large": "./assets/widgets/bg-large.png"
145
+ }
146
+ }
147
+ ```
148
+
149
+ Or a single image for all sizes: `"image": "./assets/widgets/bg.png"`
150
+
151
+ #### `icons` (for type `"list"` / `"single"`)
152
+
153
+ Bundle icon images at build time. Referenced by name at runtime.
154
+
155
+ ```json
156
+ {
157
+ "icons": {
158
+ "brainstorm": "./assets/widgets/icons/brainstorm.png",
159
+ "meditation": "./assets/widgets/icons/meditation.png",
160
+ "focus": "./assets/widgets/icons/focus.png"
161
+ }
162
+ }
163
+ ```
164
+
165
+ At runtime, use the icon name:
166
+
167
+ ```ts
168
+ { icon: 'brainstorm', title: 'Morning Focus' }
169
+ ```
170
+
171
+ On iOS, the icon is checked: if a bundled image matches the name, it's used as `Image(name)`. Otherwise, it's treated as an SF Symbol (`Image(systemName:)`).
172
+
173
+ #### `style`
174
+
175
+ ```json
176
+ {
177
+ "style": {
178
+ "backgroundColor": "#1E1E2E",
179
+ "titleColor": "#CDD6F4",
180
+ "subtitleColor": "#A6ADC8",
181
+ "accentColor": "#F38BA8"
182
+ }
183
+ }
184
+ ```
185
+
186
+ These provide widget-level defaults. Items can override per-item via `WidgetItemText` objects.
187
+
188
+ ### `ios` — Shared iOS config
189
+
190
+ | Property | Type | Default | Description |
191
+ | -------------------- | --------- | ------------------ | ---------------------------------------- |
192
+ | `deploymentTarget` | `string` | `"17.0"` | Minimum iOS version for widget extension |
193
+ | `useAppGroups` | `boolean` | `true` | Enable App Groups for data sharing |
194
+ | `appGroupIdentifier` | `string` | `group.{bundleId}` | Custom App Group ID |
195
+ | `devTeam` | `string` | — | Apple development team ID |
196
+
197
+ > **Important:** `useAppGroups: true` requires the App Group capability to be configured in your Apple Developer account. For local development without provisioning, set `useAppGroups: false` (widget won't receive data from the app).
198
+
199
+ ### `android` — Shared Android config
200
+
201
+ | Property | Type | Default | Description |
202
+ | -------------------- | -------- | ------------------------ | --------------------------------- |
203
+ | `minSdkVersion` | `number` | `26` | Minimum SDK for widget |
204
+ | `updatePeriodMillis` | `number` | `1800000` | Auto-update interval (min 30 min) |
205
+ | `minWidth` | `number` | `110` | Default widget width in dp |
206
+ | `minHeight` | `number` | `40` | Default widget height in dp |
207
+ | `resizeMode` | `string` | `"horizontal\|vertical"` | Resize behavior |
208
+
209
+ ---
210
+
211
+ ## Runtime API
212
+
213
+ ### `NNWidgets.updateWidget(widgetName, data)`
214
+
215
+ Push structured data to a specific widget and reload its timeline.
216
+
217
+ ```ts
218
+ import { NNWidgets } from "nn-widgets";
219
+
220
+ // List-type widget
221
+ await NNWidgets.updateWidget("SessionsList", {
222
+ items: [
223
+ {
224
+ icon: "star.fill",
225
+ title: "Morning Focus",
226
+ description: "25 min",
227
+ deepLink: "myapp://session/1",
228
+ },
229
+ {
230
+ icon: { url: "meditation", size: 28, radius: 14 },
231
+ title: { text: "Wind Down", color: "#CDD6F4", fontWeight: "bold" },
232
+ description: { text: "15 min", color: "#A6ADC8", fontSize: "caption" },
233
+ },
234
+ ],
235
+ });
236
+
237
+ // Single-type widget
238
+ await NNWidgets.updateWidget("StatsWidget", {
239
+ title: "Focus Time",
240
+ subtitle: "This week",
241
+ value: 142,
242
+ });
243
+ ```
244
+
245
+ #### `WidgetDataPayload`
246
+
247
+ | Field | Type | Used by | Description |
248
+ | ---------- | ------------------ | ----------------------- | --------------------- |
249
+ | `title` | `string` | single, list (fallback) | Title text |
250
+ | `subtitle` | `string` | single, list (fallback) | Subtitle text |
251
+ | `value` | `number` | single | Numeric display value |
252
+ | `items` | `WidgetListItem[]` | list | Array of list items |
253
+
254
+ #### `WidgetListItem`
255
+
256
+ | Field | Type | Default | Description |
257
+ | ------------- | -------------------------- | ------------ | ------------------------------- |
258
+ | `icon` | `string \| WidgetItemIcon` | — | Left icon (name or config) |
259
+ | `rightIcon` | `string \| WidgetItemIcon` | — | Right icon (name or config) |
260
+ | `title` | `string \| WidgetItemText` | **required** | Title text or styled text |
261
+ | `description` | `string \| WidgetItemText` | — | Description text or styled text |
262
+ | `deepLink` | `string` | — | Per-item deep link URL |
263
+
264
+ #### `WidgetItemIcon` (object form)
265
+
266
+ | Field | Type | Default | Description |
267
+ | ----------------- | -------- | ------------ | ----------------------------------- |
268
+ | `url` | `string` | **required** | SF Symbol name or bundled icon name |
269
+ | `size` | `number` | `32` | Icon size in points/dp |
270
+ | `radius` | `number` | `0` | Corner radius (size/2 = circle) |
271
+ | `backgroundColor` | `string` | — | Background color (hex) |
272
+
273
+ **Icon styling examples:**
274
+
275
+ ```ts
276
+ // Simple SF Symbol (default size 32, no background)
277
+ icon: "star.fill"
278
+
279
+ // Circular icon with background
280
+ icon: { url: "star.fill", size: 40, radius: 20, backgroundColor: "#FFD700" }
281
+
282
+ // Square icon with rounded corners
283
+ icon: { url: "brain", size: 36, radius: 8, backgroundColor: "#3B3B5C" }
284
+
285
+ // Right icon (e.g. chevron indicator)
286
+ rightIcon: { url: "chevron.right", size: 20, radius: 0 }
287
+
288
+ // Right icon as circular badge
289
+ rightIcon: { url: "checkmark.circle.fill", size: 24, radius: 12, backgroundColor: "#4CAF50" }
290
+ ```
291
+
292
+ Both `icon` (left) and `rightIcon` (right) accept the same format — either a string or a `WidgetItemIcon` object.
293
+
294
+ #### `WidgetItemText` (object form)
295
+
296
+ | Field | Type | Default | Description |
297
+ | ------------ | ------------------ | -------------------------------------------- | ---------------- |
298
+ | `text` | `string` | **required** | Text content |
299
+ | `color` | `string` | widget style | Text color (hex) |
300
+ | `fontSize` | `WidgetFontSize` | `"subheadline"` (title) / `"caption"` (desc) | Font size key |
301
+ | `fontWeight` | `WidgetFontWeight` | `"semibold"` (title) / `"regular"` (desc) | Font weight |
302
+
303
+ ### Font Size Reference
304
+
305
+ | Key | iOS (SwiftUI) | Android (sp) |
306
+ | ------------- | ----------------------- | ------------ |
307
+ | `caption2` | `.caption2` (11pt) | 11sp |
308
+ | `caption` | `.caption` (12pt) | 12sp |
309
+ | `footnote` | `.footnote` (13pt) | 13sp |
310
+ | `subheadline` | `.subheadline` (15pt) | 15sp |
311
+ | `body` | `.body` (17pt) | 17sp |
312
+ | `headline` | `.headline` (17pt bold) | 17sp bold |
313
+ | `title3` | `.title3` (20pt) | 20sp |
314
+ | `title2` | `.title2` (22pt) | 22sp |
315
+ | `title` | `.title` (28pt) | 28sp |
316
+
317
+ ### Font Weight Reference
318
+
319
+ | Key | iOS | Android |
320
+ | ---------- | ----------- | ----------------------- |
321
+ | `regular` | `.regular` | `Typeface.NORMAL` |
322
+ | `medium` | `.medium` | `Typeface.NORMAL` + 500 |
323
+ | `semibold` | `.semibold` | `Typeface.BOLD` |
324
+ | `bold` | `.bold` | `Typeface.BOLD` |
325
+
326
+ ### Max Items per Widget Size (iOS)
327
+
328
+ | Size | Max Items |
329
+ | ------------------ | --------- |
330
+ | `systemSmall` | 3 |
331
+ | `systemMedium` | 3 |
332
+ | `systemLarge` | 7 |
333
+ | `systemExtraLarge` | 7 |
334
+
335
+ ### Other API Methods
336
+
337
+ ```ts
338
+ // Legacy flat key-value data (backward compatible)
339
+ await NNWidgets.setWidgetData({ key: "value", count: 42 });
340
+
341
+ // Get stored data
342
+ const data = await NNWidgets.getWidgetData();
343
+
344
+ // Reload a specific widget
345
+ await NNWidgets.reloadWidget({ widgetName: "MyWidget" });
346
+
347
+ // Reload all widgets
348
+ await NNWidgets.reloadWidget();
349
+
350
+ // Check support
351
+ const supported = NNWidgets.isSupported();
352
+ ```
353
+
354
+ ---
355
+
356
+ ## How Data Flows
357
+
358
+ ```
359
+ ┌──────────────────────┐
360
+ │ React Native App │
361
+ │ │
362
+ │ NNWidgets.update │
363
+ │ Widget("List", { │
364
+ │ items: [...] │
365
+ │ }) │
366
+ └──────────┬───────────┘
367
+
368
+
369
+ ┌──────────────────────┐
370
+ │ Native Module │
371
+ │ (NNWidgetsModule) │
372
+ │ │
373
+ │ Stores key-value │
374
+ │ pairs to shared │
375
+ │ storage │
376
+ │ │
377
+ │ iOS: UserDefaults │
378
+ │ (App Groups) │
379
+ │ Android: Shared │
380
+ │ Preferences │
381
+ └──────────┬───────────┘
382
+
383
+
384
+ ┌──────────────────────┐
385
+ │ Widget Extension │
386
+ │ (TimelineProvider) │
387
+ │ │
388
+ │ Reads items JSON │
389
+ │ from shared storage │
390
+ │ Parses & renders │
391
+ │ the widget UI │
392
+ └──────────────────────┘
393
+ ```
394
+
395
+ ### Key Naming Convention
396
+
397
+ Data is stored with the `widget_` prefix:
398
+
399
+ | JS call | Storage key | Widget reads |
400
+ | ------------------------------------------ | --------------------- | --------------------------- |
401
+ | `updateWidget("MyList", { title: "Hi" })` | `widget_MyList_title` | `getString("MyList_title")` |
402
+ | `updateWidget("MyList", { items: [...] })` | `widget_MyList_items` | `getJSON("MyList_items")` |
403
+ | `setWidgetData({ foo: "bar" })` | `widget_foo` | `getString("foo")` |
404
+
405
+ ---
406
+
407
+ ## Full Example: `app.json`
408
+
409
+ ```json
410
+ {
411
+ "plugins": [
412
+ [
413
+ "nn-widgets",
414
+ {
415
+ "widgets": [
416
+ {
417
+ "name": "MainWidget",
418
+ "displayName": "My App",
419
+ "type": "single",
420
+ "deepLinkUrl": "myapp://widget",
421
+ "showAppIcon": true,
422
+ "widgetFamilies": ["systemSmall"]
423
+ },
424
+ {
425
+ "name": "RecentSessions",
426
+ "displayName": "Recent Sessions",
427
+ "type": "list",
428
+ "deepLinkUrl": "myapp://sessions",
429
+ "widgetFamilies": ["systemMedium", "systemLarge"],
430
+ "fallbackTitle": "Recent Sessions",
431
+ "fallbackSubtitle": "Open app to see sessions",
432
+ "icons": {
433
+ "focus": "./assets/widgets/icons/focus.png",
434
+ "relax": "./assets/widgets/icons/relax.png"
435
+ },
436
+ "style": {
437
+ "backgroundColor": "#1E1E2E",
438
+ "titleColor": "#CDD6F4",
439
+ "subtitleColor": "#A6ADC8",
440
+ "accentColor": "#F38BA8"
441
+ }
442
+ },
443
+ {
444
+ "name": "BannerWidget",
445
+ "displayName": "Banner",
446
+ "deepLinkUrl": "myapp://banner",
447
+ "widgetFamilies": ["systemMedium"],
448
+ "image": {
449
+ "medium": "./assets/widgets/banner.png"
450
+ }
451
+ },
452
+ {
453
+ "name": "QuickGrid",
454
+ "displayName": "Quick Grid",
455
+ "type": "grid",
456
+ "gridLayout": "3x2",
457
+ "deepLinkUrl": "myapp://grid",
458
+ "widgetFamilies": ["systemMedium", "systemLarge"],
459
+ "fallbackTitle": "Quick Grid",
460
+ "fallbackSubtitle": "Open app to set up",
461
+ "style": {
462
+ "backgroundColor": "#1A1A2E",
463
+ "titleColor": "#E0E0E0",
464
+ "accentColor": "#BB86FC"
465
+ }
466
+ }
467
+ ],
468
+ "ios": {
469
+ "devTeam": "YOUR_TEAM_ID",
470
+ "useAppGroups": true,
471
+ "appGroupIdentifier": "group.com.myapp"
472
+ }
473
+ }
474
+ ]
475
+ ]
476
+ }
477
+ ```
478
+
479
+ ## Full Example: Pushing data from a screen
480
+
481
+ ```tsx
482
+ import React, { useEffect } from 'react';
483
+ import { NNWidgets } from 'nn-widgets';
484
+
485
+ function SessionsScreen({ sessions }) {
486
+ useEffect(() => {
487
+ // Update list widget with styled icons
488
+ NNWidgets.updateWidget('RecentSessions', {
489
+ items: sessions.slice(0, 7).map((s) => ({
490
+ icon: { url: s.type, size: 32, radius: 8, backgroundColor: '#3B3B5C' },
491
+ title: { text: s.name, fontWeight: 'semibold' },
492
+ description: { text: `${s.duration} min`, fontSize: 'caption' },
493
+ rightIcon: { url: 'chevron.right', size: 16 },
494
+ deepLink: `myapp://session/${s.id}`,
495
+ })),
496
+ });
497
+ }, [sessions]);
498
+
499
+ return (/* ... */);
500
+ }
501
+ ```
502
+
503
+ ## Full Example: Grid widget data
504
+
505
+ ```tsx
506
+ // Grid widget with 3x2 layout (3 columns, 2 rows, max 6 items)
507
+ await NNWidgets.updateWidget('QuickGrid', {
508
+ items: [
509
+ {
510
+ icon: { url: 'star.fill', size: 40, radius: 20, backgroundColor: '#FFD700' },
511
+ title: 'Favorites',
512
+ deepLink: 'myapp://favorites',
513
+ },
514
+ {
515
+ icon: { url: 'clock.fill', size: 40, radius: 20, backgroundColor: '#4A90D9' },
516
+ title: 'Recent',
517
+ deepLink: 'myapp://recent',
518
+ },
519
+ {
520
+ icon: { url: 'bolt.fill', size: 40, radius: 20, backgroundColor: '#FF6B6B' },
521
+ title: 'Quick Start',
522
+ deepLink: 'myapp://quick',
523
+ },
524
+ {
525
+ icon: { url: 'person.2.fill', size: 40, radius: 20, backgroundColor: '#66BB6A' },
526
+ title: 'Team',
527
+ deepLink: 'myapp://team',
528
+ },
529
+ {
530
+ icon: { url: 'chart.bar.fill', size: 40, radius: 20, backgroundColor: '#AB47BC' },
531
+ title: 'Stats',
532
+ deepLink: 'myapp://stats',
533
+ },
534
+ {
535
+ icon: { url: 'gearshape.fill', size: 40, radius: 20, backgroundColor: '#78909C' },
536
+ title: 'Settings',
537
+ deepLink: 'myapp://settings',
538
+ },
539
+ ],
540
+ });
541
+ ```
542
+
543
+ ---
544
+
545
+ ## Troubleshooting
546
+
547
+ ### Widget shows fallback text ("Open app to start")
548
+
549
+ - The app hasn't called `updateWidget()` yet, or `useAppGroups` is `false`
550
+ - Ensure `useAppGroups: true` in `ios` config and the App Group is provisioned
551
+
552
+ ### Widget doesn't update after calling `updateWidget()`
553
+
554
+ - `updateWidget()` automatically calls `reloadWidget()` after storing data
555
+ - iOS may delay widget updates. Force refresh by removing and re-adding the widget
556
+
557
+ ### Build error: "App Group not configured"
558
+
559
+ - Add the App Group capability in Apple Developer portal
560
+ - The App Group ID must match `appGroupIdentifier` (default: `group.{bundleIdentifier}`)
561
+ - For local dev without provisioning, set `useAppGroups: false`
562
+
563
+ ### Icons not showing
564
+
565
+ - Bundled icons: ensure paths in `icons` config are correct and files exist
566
+ - SF Symbols: use exact symbol names (e.g., `"star.fill"`, not `"star"`)
567
+ - Android: icon names are lowercased for drawable resources
568
+
569
+ ## Requirements
570
+
571
+ - Expo SDK 53+
572
+ - iOS 17.0+ (for WidgetKit)
573
+ - Android API 26+ (for App Widgets)
574
+
575
+ ## License
576
+
577
+ MIT
@@ -0,0 +1,90 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+ apply plugin: 'maven-publish'
4
+
5
+ group = 'expo.modules.nnwidgets'
6
+ version = '0.1.0'
7
+
8
+ buildscript {
9
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
10
+ if (expoModulesCorePlugin.exists()) {
11
+ apply from: expoModulesCorePlugin
12
+ applyKotlinExpoModulesCorePlugin()
13
+ }
14
+
15
+ ext.safeExtGet = { prop, fallback ->
16
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
17
+ }
18
+
19
+ ext.getKotlinVersion = {
20
+ if (ext.has("kotlinVersion")) {
21
+ ext.kotlinVersion()
22
+ } else {
23
+ ext.safeExtGet("kotlinVersion", "1.8.10")
24
+ }
25
+ }
26
+
27
+ repositories {
28
+ mavenCentral()
29
+ }
30
+
31
+ dependencies {
32
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
33
+ }
34
+ }
35
+
36
+ afterEvaluate {
37
+ publishing {
38
+ publications {
39
+ release(MavenPublication) {
40
+ from components.release
41
+ }
42
+ }
43
+ repositories {
44
+ maven {
45
+ url = mavenLocal().url
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ android {
52
+ namespace = "expo.modules.nnwidgets"
53
+
54
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
55
+
56
+ compileOptions {
57
+ sourceCompatibility JavaVersion.VERSION_17
58
+ targetCompatibility JavaVersion.VERSION_17
59
+ }
60
+
61
+ kotlinOptions {
62
+ jvmTarget = JavaVersion.VERSION_17.majorVersion
63
+ }
64
+
65
+ defaultConfig {
66
+ minSdkVersion safeExtGet("minSdkVersion", 26)
67
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
68
+ versionCode 1
69
+ versionName "0.1.0"
70
+ }
71
+
72
+ lintOptions {
73
+ abortOnError false
74
+ }
75
+
76
+ publishing {
77
+ singleVariant("release") {
78
+ withSourcesJar()
79
+ }
80
+ }
81
+ }
82
+
83
+ repositories {
84
+ mavenCentral()
85
+ }
86
+
87
+ dependencies {
88
+ implementation project(':expo-modules-core')
89
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
90
+ }
package/app.plugin.js ADDED
@@ -0,0 +1,4 @@
1
+ // nn-widgets Expo Config Plugin Entry Point
2
+ // This file loads the compiled plugin from the plugin/build directory
3
+
4
+ module.exports = require("./plugin/build").default;