expo-widgets 56.0.13 → 56.0.15

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/android/build.gradle +38 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/java/expo/modules/widgets/ExpoWidgetsAppWidgetProvider.kt +10 -0
  5. package/android/src/main/java/expo/modules/widgets/ExpoWidgetsGlanceWidget.kt +17 -0
  6. package/android/src/main/java/expo/modules/widgets/WidgetsModule.kt +10 -0
  7. package/expo-module.config.json +4 -1
  8. package/package.json +5 -5
  9. package/plugin/build/android/resourceNames.d.ts +5 -0
  10. package/plugin/build/android/resourceNames.js +29 -0
  11. package/plugin/build/android/withAndroidWidgetFiles.d.ts +4 -0
  12. package/plugin/build/android/withAndroidWidgetFiles.js +142 -0
  13. package/plugin/build/android/withAndroidWidgetManifest.d.ts +4 -0
  14. package/plugin/build/android/withAndroidWidgetManifest.js +58 -0
  15. package/plugin/build/android/withAndroidWidgets.d.ts +6 -0
  16. package/plugin/build/android/withAndroidWidgets.js +19 -0
  17. package/plugin/build/ios/withIosWidgets.d.ts +11 -0
  18. package/plugin/build/ios/withIosWidgets.js +81 -0
  19. package/plugin/build/{withWidgetSourceFiles.d.ts → ios/withWidgetSourceFiles.d.ts} +1 -1
  20. package/plugin/build/{withWidgetSourceFiles.js → ios/withWidgetSourceFiles.js} +18 -13
  21. package/plugin/build/types/WidgetConfig.type.d.ts +17 -1
  22. package/plugin/build/types/WidgetFamily.type.d.ts +10 -9
  23. package/plugin/build/types/WidgetFamily.type.js +9 -10
  24. package/plugin/build/withWidgets.js +6 -63
  25. package/plugin/src/android/resourceNames.ts +29 -0
  26. package/plugin/src/android/withAndroidWidgetFiles.ts +143 -0
  27. package/plugin/src/android/withAndroidWidgetManifest.ts +94 -0
  28. package/plugin/src/android/withAndroidWidgets.ts +21 -0
  29. package/plugin/src/ios/withIosWidgets.ts +99 -0
  30. package/plugin/src/{withWidgetSourceFiles.ts → ios/withWidgetSourceFiles.ts} +20 -14
  31. package/plugin/src/types/WidgetConfig.type.ts +20 -1
  32. package/plugin/src/types/WidgetFamily.type.ts +11 -9
  33. package/plugin/src/withWidgets.ts +7 -71
  34. package/plugin/tsconfig.tsbuildinfo +1 -0
  35. /package/plugin/build/{withAppGroupEntitlements.d.ts → ios/withAppGroupEntitlements.d.ts} +0 -0
  36. /package/plugin/build/{withAppGroupEntitlements.js → ios/withAppGroupEntitlements.js} +0 -0
  37. /package/plugin/build/{withAppInfoPlist.d.ts → ios/withAppInfoPlist.d.ts} +0 -0
  38. /package/plugin/build/{withAppInfoPlist.js → ios/withAppInfoPlist.js} +0 -0
  39. /package/plugin/build/{withEasConfig.d.ts → ios/withEasConfig.d.ts} +0 -0
  40. /package/plugin/build/{withEasConfig.js → ios/withEasConfig.js} +0 -0
  41. /package/plugin/build/{withIosWarning.d.ts → ios/withIosWarning.d.ts} +0 -0
  42. /package/plugin/build/{withIosWarning.js → ios/withIosWarning.js} +0 -0
  43. /package/plugin/build/{withPodsLinking.d.ts → ios/withPodsLinking.d.ts} +0 -0
  44. /package/plugin/build/{withPodsLinking.js → ios/withPodsLinking.js} +0 -0
  45. /package/plugin/build/{withPushNotifications.d.ts → ios/withPushNotifications.d.ts} +0 -0
  46. /package/plugin/build/{withPushNotifications.js → ios/withPushNotifications.js} +0 -0
  47. /package/plugin/build/{xcode → ios/xcode}/addBuildPhases.d.ts +0 -0
  48. /package/plugin/build/{xcode → ios/xcode}/addBuildPhases.js +0 -0
  49. /package/plugin/build/{xcode → ios/xcode}/addPbxGroup.d.ts +0 -0
  50. /package/plugin/build/{xcode → ios/xcode}/addPbxGroup.js +0 -0
  51. /package/plugin/build/{xcode → ios/xcode}/addProductFile.d.ts +0 -0
  52. /package/plugin/build/{xcode → ios/xcode}/addProductFile.js +0 -0
  53. /package/plugin/build/{xcode → ios/xcode}/addTargetDependency.d.ts +0 -0
  54. /package/plugin/build/{xcode → ios/xcode}/addTargetDependency.js +0 -0
  55. /package/plugin/build/{xcode → ios/xcode}/addToPbxNativeTargetSection.d.ts +0 -0
  56. /package/plugin/build/{xcode → ios/xcode}/addToPbxNativeTargetSection.js +0 -0
  57. /package/plugin/build/{xcode → ios/xcode}/addToPbxProjectSection.d.ts +0 -0
  58. /package/plugin/build/{xcode → ios/xcode}/addToPbxProjectSection.js +0 -0
  59. /package/plugin/build/{xcode → ios/xcode}/addXCConfigurationList.d.ts +0 -0
  60. /package/plugin/build/{xcode → ios/xcode}/addXCConfigurationList.js +0 -0
  61. /package/plugin/build/{xcode → ios/xcode}/withTargetXcodeProject.d.ts +0 -0
  62. /package/plugin/build/{xcode → ios/xcode}/withTargetXcodeProject.js +0 -0
  63. /package/plugin/src/{withAppGroupEntitlements.ts → ios/withAppGroupEntitlements.ts} +0 -0
  64. /package/plugin/src/{withAppInfoPlist.ts → ios/withAppInfoPlist.ts} +0 -0
  65. /package/plugin/src/{withEasConfig.ts → ios/withEasConfig.ts} +0 -0
  66. /package/plugin/src/{withIosWarning.ts → ios/withIosWarning.ts} +0 -0
  67. /package/plugin/src/{withPodsLinking.ts → ios/withPodsLinking.ts} +0 -0
  68. /package/plugin/src/{withPushNotifications.ts → ios/withPushNotifications.ts} +0 -0
  69. /package/plugin/src/{xcode → ios/xcode}/addBuildPhases.ts +0 -0
  70. /package/plugin/src/{xcode → ios/xcode}/addPbxGroup.ts +0 -0
  71. /package/plugin/src/{xcode → ios/xcode}/addProductFile.ts +0 -0
  72. /package/plugin/src/{xcode → ios/xcode}/addTargetDependency.ts +0 -0
  73. /package/plugin/src/{xcode → ios/xcode}/addToPbxNativeTargetSection.ts +0 -0
  74. /package/plugin/src/{xcode → ios/xcode}/addToPbxProjectSection.ts +0 -0
  75. /package/plugin/src/{xcode → ios/xcode}/addXCConfigurationList.ts +0 -0
  76. /package/plugin/src/{xcode → ios/xcode}/withTargetXcodeProject.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -10,6 +10,22 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 56.0.15 — 2026-05-26
14
+
15
+ ### 🎉 New features
16
+
17
+ - [Android] Create base android package. ([#46090](https://github.com/expo/expo/pull/46090) by [@jakex7](https://github.com/jakex7))
18
+ - [plugin] Create config plugin for android. ([#46091](https://github.com/expo/expo/pull/46091) by [@jakex7](https://github.com/jakex7))
19
+
20
+ ### 🐛 Bug fixes
21
+
22
+ - Allow string widget family values in the typed config plugin. ([#46133](https://github.com/expo/expo/issues/46133) by [@eliotgevers](https://github.com/eliotgevers))
23
+ - [plugin] Inherit `ios.deploymentTarget` from app config for generated widget extension targets. ([#46193](https://github.com/expo/expo/pull/46193) by [@eliotgevers](https://github.com/eliotgevers))
24
+
25
+ ## 56.0.14 — 2026-05-23
26
+
27
+ _This version does not introduce any user-facing changes._
28
+
13
29
  ## 56.0.13 — 2026-05-21
14
30
 
15
31
  _This version does not introduce any user-facing changes._
@@ -0,0 +1,38 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+ dependencies {
7
+ classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:${kotlinVersion}")
8
+ }
9
+ }
10
+
11
+ apply plugin: 'com.android.library'
12
+ apply plugin: 'expo-module-gradle-plugin'
13
+ apply plugin: 'org.jetbrains.kotlin.plugin.compose'
14
+
15
+ group = 'expo.modules.widgets'
16
+ version = '56.0.15'
17
+
18
+ expoModule {
19
+ canBePublished false
20
+ }
21
+
22
+ android {
23
+ namespace "expo.modules.widgets"
24
+ defaultConfig {
25
+ versionCode 1
26
+ versionName '56.0.15'
27
+ }
28
+
29
+ buildFeatures {
30
+ prefab true
31
+ compose true
32
+ }
33
+ }
34
+
35
+ dependencies {
36
+ api 'androidx.glance:glance:1.2.0-rc01'
37
+ api 'androidx.glance:glance-appwidget:1.2.0-rc01'
38
+ }
@@ -0,0 +1,2 @@
1
+ <manifest>
2
+ </manifest>
@@ -0,0 +1,10 @@
1
+ package expo.modules.widgets
2
+
3
+ import androidx.glance.appwidget.GlanceAppWidget
4
+ import androidx.glance.appwidget.GlanceAppWidgetReceiver
5
+
6
+ open class ExpoWidgetsAppWidgetProvider(
7
+ private val widgetName: String
8
+ ) : GlanceAppWidgetReceiver() {
9
+ override val glanceAppWidget: GlanceAppWidget = ExpoWidgetsGlanceWidget(widgetName)
10
+ }
@@ -0,0 +1,17 @@
1
+ package expo.modules.widgets
2
+
3
+ import android.content.Context
4
+ import androidx.glance.GlanceId
5
+ import androidx.glance.appwidget.GlanceAppWidget
6
+ import androidx.glance.appwidget.provideContent
7
+ import androidx.glance.text.Text
8
+
9
+ internal class ExpoWidgetsGlanceWidget(
10
+ private val widgetName: String
11
+ ) : GlanceAppWidget() {
12
+ override suspend fun provideGlance(context: Context, id: GlanceId) {
13
+ provideContent {
14
+ Text(widgetName)
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ package expo.modules.widgets
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+
6
+ class WidgetsModule : Module() {
7
+ override fun definition() = ModuleDefinition {
8
+ Name("ExpoWidgets")
9
+ }
10
+ }
@@ -1,6 +1,9 @@
1
1
  {
2
- "platforms": ["apple"],
2
+ "platforms": ["apple", "android"],
3
3
  "apple": {
4
4
  "modules": ["WidgetsModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.widgets.WidgetsModule"]
5
8
  }
6
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-widgets",
3
- "version": "56.0.13",
3
+ "version": "56.0.15",
4
4
  "description": "Widgets.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "homepage": "https://docs.expo.dev/versions/latest/sdk/widgets/",
23
23
  "dependencies": {
24
24
  "@expo/plist": "^0.7.0",
25
- "@expo/ui": "~56.0.12"
25
+ "@expo/ui": "~56.0.14"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@expo/spawn-async": "^1.8.0",
@@ -30,8 +30,8 @@
30
30
  "@types/react": "~19.2.0",
31
31
  "react-native": "0.85.3",
32
32
  "resolve-workspace-root": "^2.0.0",
33
- "expo": "56.0.3",
34
- "expo-module-scripts": "56.0.2"
33
+ "expo-module-scripts": "56.0.2",
34
+ "expo": "56.0.5"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "expo": "*",
@@ -44,7 +44,7 @@
44
44
  "./scripts/xcode-build-bundle.sh"
45
45
  ]
46
46
  },
47
- "gitHead": "e50d2c763bc89853aaa119d5729ba21197d230a7",
47
+ "gitHead": "f67a101bcbe56114e982184834b93da7bbed00af",
48
48
  "scripts": {
49
49
  "build": "expo-module build",
50
50
  "build:plugin": "expo-module build plugin",
@@ -0,0 +1,5 @@
1
+ import { WidgetConfig } from '../types/WidgetConfig.type';
2
+ export declare const getProviderClassName: (widget: WidgetConfig) => string;
3
+ export declare const getWidgetInfoResourceName: (widget: WidgetConfig) => string;
4
+ export declare const getWidgetDisplayNameResourceName: (widget: WidgetConfig) => string;
5
+ export declare const getWidgetDescriptionResourceName: (widget: WidgetConfig) => string;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getWidgetDescriptionResourceName = exports.getWidgetDisplayNameResourceName = exports.getWidgetInfoResourceName = exports.getProviderClassName = void 0;
4
+ const toAndroidResourceName = (name) => {
5
+ const resourceName = name
6
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
7
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
8
+ .replace(/[^A-Za-z0-9_]/g, '_')
9
+ .replace(/_+/g, '_')
10
+ .replace(/^_+|_+$/g, '')
11
+ .toLowerCase();
12
+ return /^[a-z]/.test(resourceName) ? resourceName : `widget_${resourceName}`;
13
+ };
14
+ const getProviderClassName = (widget) => {
15
+ return `${widget.name}Provider`;
16
+ };
17
+ exports.getProviderClassName = getProviderClassName;
18
+ const getWidgetInfoResourceName = (widget) => {
19
+ return `${toAndroidResourceName(widget.name)}_info`;
20
+ };
21
+ exports.getWidgetInfoResourceName = getWidgetInfoResourceName;
22
+ const getWidgetDisplayNameResourceName = (widget) => {
23
+ return `${toAndroidResourceName(widget.name)}_display_name`;
24
+ };
25
+ exports.getWidgetDisplayNameResourceName = getWidgetDisplayNameResourceName;
26
+ const getWidgetDescriptionResourceName = (widget) => {
27
+ return `${toAndroidResourceName(widget.name)}_description`;
28
+ };
29
+ exports.getWidgetDescriptionResourceName = getWidgetDescriptionResourceName;
@@ -0,0 +1,4 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import { WidgetConfig } from '../types/WidgetConfig.type';
3
+ declare const withAndroidWidgetFiles: ConfigPlugin<WidgetConfig[]>;
4
+ export default withAndroidWidgetFiles;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const config_plugins_1 = require("expo/config-plugins");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const resourceNames_1 = require("./resourceNames");
40
+ const withAndroidWidgetFiles = (config, widgets) => {
41
+ return (0, config_plugins_1.withDangerousMod)(config, [
42
+ 'android',
43
+ async (config) => {
44
+ const androidPackage = config_plugins_1.AndroidConfig.Package.getPackage(config);
45
+ if (typeof androidPackage !== 'string' ||
46
+ !/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(androidPackage)) {
47
+ throw new Error('Android package is required for expo-widgets. Please set `android.package` in `app.json` or `app.config.js`.');
48
+ }
49
+ const projectRoot = config.modRequest.platformProjectRoot;
50
+ const packageDirectory = path.join(projectRoot, 'app/src/main/java', ...androidPackage.split('.'));
51
+ const xmlDirectory = path.join(projectRoot, 'app/src/main/res/xml');
52
+ const valuesDirectory = path.join(projectRoot, 'app/src/main/res/values');
53
+ const widgetsXmlPath = path.join(xmlDirectory, 'expo_widgets.xml');
54
+ fs.mkdirSync(packageDirectory, { recursive: true });
55
+ fs.mkdirSync(xmlDirectory, { recursive: true });
56
+ fs.mkdirSync(valuesDirectory, { recursive: true });
57
+ if (fs.existsSync(widgetsXmlPath)) {
58
+ fs.rmSync(widgetsXmlPath);
59
+ }
60
+ fs.writeFileSync(widgetsXmlPath, createWidgetStringsXml(widgets));
61
+ for (const widget of widgets) {
62
+ const providerPath = path.join(packageDirectory, `${(0, resourceNames_1.getProviderClassName)(widget)}.kt`);
63
+ if (fs.existsSync(providerPath)) {
64
+ fs.rmSync(providerPath);
65
+ }
66
+ fs.writeFileSync(path.join(packageDirectory, `${(0, resourceNames_1.getProviderClassName)(widget)}.kt`), createWidgetProviderKt(androidPackage, widget));
67
+ const widgetInfoPath = path.join(xmlDirectory, `${(0, resourceNames_1.getWidgetInfoResourceName)(widget)}.xml`);
68
+ if (fs.existsSync(widgetInfoPath)) {
69
+ fs.rmSync(widgetInfoPath);
70
+ }
71
+ fs.writeFileSync(path.join(xmlDirectory, `${(0, resourceNames_1.getWidgetInfoResourceName)(widget)}.xml`), createWidgetInfoXml(widget));
72
+ }
73
+ return config;
74
+ },
75
+ ]);
76
+ };
77
+ const createWidgetProviderKt = (androidPackage, widget) => {
78
+ return `package ${androidPackage}
79
+
80
+ import expo.modules.widgets.ExpoWidgetsAppWidgetProvider
81
+
82
+ class ${(0, resourceNames_1.getProviderClassName)(widget)} : ExpoWidgetsAppWidgetProvider(${JSON.stringify(widget.name)})
83
+ `;
84
+ };
85
+ const createWidgetInfoXml = (widget) => {
86
+ const android = getAndroidWidgetConfig(widget);
87
+ return `<?xml version="1.0" encoding="utf-8"?>
88
+ <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
89
+ android:minWidth="${android.minWidth}dp"
90
+ android:minHeight="${android.minHeight}dp"
91
+ android:targetCellWidth="${android.targetCellWidth}"
92
+ android:targetCellHeight="${android.targetCellHeight}"
93
+ android:updatePeriodMillis="0"
94
+ android:initialLayout="@layout/glance_default_loading_layout"
95
+ android:description="@string/${(0, resourceNames_1.getWidgetDescriptionResourceName)(widget)}"
96
+ android:resizeMode="${getWidgetResizeMode(widget)}"
97
+ android:widgetCategory="home_screen" />
98
+ `;
99
+ };
100
+ const getWidgetResizeMode = (widget) => {
101
+ const resizeMode = widget.android?.resizeMode;
102
+ switch (resizeMode) {
103
+ case 'none':
104
+ return 'none';
105
+ case 'horizontal':
106
+ return 'horizontal';
107
+ case 'vertical':
108
+ return 'vertical';
109
+ case 'both':
110
+ default:
111
+ return 'horizontal|vertical';
112
+ }
113
+ };
114
+ const getAndroidWidgetConfig = (widget) => {
115
+ return {
116
+ minWidth: widget.android?.minWidth ?? 180,
117
+ minHeight: widget.android?.minHeight ?? 110,
118
+ targetCellWidth: widget.android?.targetCellWidth ?? 4,
119
+ targetCellHeight: widget.android?.targetCellHeight ?? 2,
120
+ };
121
+ };
122
+ const escapeXmlSpecialChars = (value) => {
123
+ return value
124
+ .replace(/&/g, '&amp;')
125
+ .replace(/"/g, '&quot;')
126
+ .replace(/'/g, '&apos;')
127
+ .replace(/</g, '&lt;')
128
+ .replace(/>/g, '&gt;');
129
+ };
130
+ const createWidgetStringsXml = (widgets) => {
131
+ return `<?xml version="1.0" encoding="utf-8"?>
132
+ <resources>
133
+ ${widgets
134
+ .map((widget) => {
135
+ return ` <string name="${(0, resourceNames_1.getWidgetDisplayNameResourceName)(widget)}">${escapeXmlSpecialChars(widget.displayName)}</string>
136
+ <string name="${(0, resourceNames_1.getWidgetDescriptionResourceName)(widget)}">${escapeXmlSpecialChars(widget.description)}</string>`;
137
+ })
138
+ .join('\n')}
139
+ </resources>
140
+ `;
141
+ };
142
+ exports.default = withAndroidWidgetFiles;
@@ -0,0 +1,4 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import { WidgetConfig } from '../types/WidgetConfig.type';
3
+ declare const withAndroidWidgetManifest: ConfigPlugin<WidgetConfig[]>;
4
+ export default withAndroidWidgetManifest;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("expo/config-plugins");
4
+ const resourceNames_1 = require("./resourceNames");
5
+ const EXPO_WIDGETS_INTERACTION_ACTION = 'expo.modules.widgets.ACTION_WIDGET_INTERACTION';
6
+ const EXPO_WIDGETS_NAME_METADATA = 'expo.modules.widgets.NAME';
7
+ const createWidgetReceiver = (widget) => {
8
+ return {
9
+ $: {
10
+ 'android:name': `.${(0, resourceNames_1.getProviderClassName)(widget)}`,
11
+ 'android:exported': 'true',
12
+ 'android:label': `@string/${(0, resourceNames_1.getWidgetDisplayNameResourceName)(widget)}`,
13
+ },
14
+ 'intent-filter': [
15
+ {
16
+ action: [
17
+ {
18
+ $: {
19
+ 'android:name': 'android.appwidget.action.APPWIDGET_UPDATE',
20
+ },
21
+ },
22
+ {
23
+ $: {
24
+ 'android:name': EXPO_WIDGETS_INTERACTION_ACTION,
25
+ },
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ 'meta-data': [
31
+ {
32
+ $: {
33
+ 'android:name': 'android.appwidget.provider',
34
+ 'android:resource': `@xml/${(0, resourceNames_1.getWidgetInfoResourceName)(widget)}`,
35
+ },
36
+ },
37
+ {
38
+ $: {
39
+ 'android:name': EXPO_WIDGETS_NAME_METADATA,
40
+ 'android:value': widget.name,
41
+ },
42
+ },
43
+ ],
44
+ };
45
+ };
46
+ const withAndroidWidgetManifest = (config, widgets) => {
47
+ return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
48
+ const mainApplication = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
49
+ const receivers = (mainApplication.receiver ?? []);
50
+ const isNotWidgetReceiver = (receiver) => !receiver['meta-data']?.some((metaData) => metaData.$['android:name'] === EXPO_WIDGETS_NAME_METADATA);
51
+ mainApplication.receiver = [
52
+ ...receivers.filter(isNotWidgetReceiver),
53
+ ...widgets.map(createWidgetReceiver),
54
+ ];
55
+ return config;
56
+ });
57
+ };
58
+ exports.default = withAndroidWidgetManifest;
@@ -0,0 +1,6 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import { WidgetConfig } from '../types/WidgetConfig.type';
3
+ declare const withAndroidWidgets: ConfigPlugin<{
4
+ widgets: WidgetConfig[];
5
+ }>;
6
+ export default withAndroidWidgets;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const withAndroidWidgetFiles_1 = __importDefault(require("./withAndroidWidgetFiles"));
7
+ const withAndroidWidgetManifest_1 = __importDefault(require("./withAndroidWidgetManifest"));
8
+ const withAndroidWidgets = (config, { widgets }) => {
9
+ if (widgets.length === 0) {
10
+ return config;
11
+ }
12
+ const androidWidgets = widgets.filter((widget) => widget.android !== null);
13
+ let nextConfig = config;
14
+ if (androidWidgets.length > 0) {
15
+ nextConfig = (0, withAndroidWidgetFiles_1.default)(nextConfig, androidWidgets);
16
+ }
17
+ return (0, withAndroidWidgetManifest_1.default)(nextConfig, androidWidgets);
18
+ };
19
+ exports.default = withAndroidWidgets;
@@ -0,0 +1,11 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import { WidgetConfig } from '../types/WidgetConfig.type';
3
+ type IosWidgetsProps = {
4
+ bundleIdentifier?: string;
5
+ groupIdentifier?: string;
6
+ enablePushNotifications?: boolean;
7
+ frequentUpdates?: boolean;
8
+ widgets: WidgetConfig[];
9
+ };
10
+ declare const withIosWidgets: ConfigPlugin<IosWidgetsProps>;
11
+ export default withIosWidgets;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const config_plugins_1 = require("expo/config-plugins");
7
+ const withAppGroupEntitlements_1 = __importDefault(require("./withAppGroupEntitlements"));
8
+ const withAppInfoPlist_1 = __importDefault(require("./withAppInfoPlist"));
9
+ const withEasConfig_1 = __importDefault(require("./withEasConfig"));
10
+ const withIosWarning_1 = __importDefault(require("./withIosWarning"));
11
+ const withPodsLinking_1 = __importDefault(require("./withPodsLinking"));
12
+ const withPushNotifications_1 = __importDefault(require("./withPushNotifications"));
13
+ const withWidgetSourceFiles_1 = __importDefault(require("./withWidgetSourceFiles"));
14
+ const withTargetXcodeProject_1 = __importDefault(require("./xcode/withTargetXcodeProject"));
15
+ const withIosWidgets = (config, props) => {
16
+ const shouldConfigureIos = !!config.ios?.bundleIdentifier ||
17
+ !!props.bundleIdentifier ||
18
+ !!props.groupIdentifier ||
19
+ props.enablePushNotifications === true ||
20
+ props.frequentUpdates === true;
21
+ if (!shouldConfigureIos) {
22
+ return config;
23
+ }
24
+ let plugins = [];
25
+ const deploymentTarget = config.ios?.deploymentTarget ?? '16.4';
26
+ const targetName = 'ExpoWidgetsTarget';
27
+ const widgets = props.widgets.filter((widget) => widget.ios !== null);
28
+ const enablePushNotifications = props.enablePushNotifications ?? false;
29
+ const frequentUpdates = props.frequentUpdates ?? false;
30
+ let bundleIdentifier = props.bundleIdentifier;
31
+ if (!bundleIdentifier) {
32
+ bundleIdentifier = `${config.ios?.bundleIdentifier}.${targetName}`;
33
+ plugins.push([
34
+ withIosWarning_1.default,
35
+ {
36
+ property: 'bundleIdentifier',
37
+ warning: `Expo Widgets: No bundle identifier provided, using fallback: ${bundleIdentifier}.`,
38
+ },
39
+ ]);
40
+ }
41
+ let groupIdentifier = props.groupIdentifier;
42
+ if (!groupIdentifier) {
43
+ if (!config.ios?.bundleIdentifier) {
44
+ throw new Error('iOS bundle identifier is required. Please set `ios.bundleIdentifier` in `app.json` or `app.config.js`');
45
+ }
46
+ groupIdentifier = `group.${config.ios.bundleIdentifier}`;
47
+ plugins.push([
48
+ withIosWarning_1.default,
49
+ {
50
+ property: 'groupIdentifier',
51
+ warning: `Expo Widgets: No group identifier provided, using fallback: ${groupIdentifier}.`,
52
+ },
53
+ ]);
54
+ }
55
+ let sharedFiles = [];
56
+ const setFiles = (files) => {
57
+ sharedFiles = [...sharedFiles, ...files];
58
+ };
59
+ const getFileUris = () => sharedFiles;
60
+ plugins = [
61
+ ...plugins,
62
+ [withEasConfig_1.default, { targetName, bundleIdentifier, groupIdentifier }],
63
+ [withPodsLinking_1.default, { targetName }],
64
+ [withWidgetSourceFiles_1.default, { targetName, widgets, groupIdentifier, onFilesGenerated: setFiles }],
65
+ [withAppInfoPlist_1.default, { frequentUpdates, groupIdentifier }],
66
+ [withPushNotifications_1.default, { enablePushNotifications }],
67
+ [withAppGroupEntitlements_1.default, { groupIdentifier }],
68
+ [
69
+ withTargetXcodeProject_1.default,
70
+ {
71
+ targetName,
72
+ bundleIdentifier,
73
+ deploymentTarget,
74
+ appleTeamId: config.ios?.appleTeamId,
75
+ getFileUris,
76
+ },
77
+ ],
78
+ ];
79
+ return (0, config_plugins_1.withPlugins)(config, plugins);
80
+ };
81
+ exports.default = withIosWidgets;
@@ -1,5 +1,5 @@
1
1
  import { ConfigPlugin } from 'expo/config-plugins';
2
- import { WidgetConfig } from './types/WidgetConfig.type';
2
+ import { WidgetConfig } from '../types/WidgetConfig.type';
3
3
  type WidgetSourceFilesProps = {
4
4
  targetName: string;
5
5
  groupIdentifier: string;
@@ -40,7 +40,7 @@ const plist_1 = __importDefault(require("@expo/plist"));
40
40
  const config_plugins_1 = require("expo/config-plugins");
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
- const WidgetFamily_type_1 = require("./types/WidgetFamily.type");
43
+ const WidgetFamily_type_1 = require("../types/WidgetFamily.type");
44
44
  const VALID_WIDGET_FAMILIES = new Set(Object.values(WidgetFamily_type_1.WidgetFamily));
45
45
  function assertSwiftIdentifier(value, label) {
46
46
  if (typeof value !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
@@ -54,11 +54,13 @@ function assertWidgetFamily(value) {
54
54
  }
55
55
  function validateWidget(widget) {
56
56
  assertSwiftIdentifier(widget.name, 'widget name');
57
- for (const family of widget.supportedFamilies) {
57
+ const supportedFamilies = widget.ios?.supportedFamilies ?? widget.supportedFamilies ?? [];
58
+ for (const family of supportedFamilies) {
58
59
  assertWidgetFamily(family);
59
60
  }
60
- if (widget.configuration) {
61
- for (const [paramName, param] of Object.entries(widget.configuration.parameters)) {
61
+ const configuration = widget.ios?.configuration ?? widget.configuration;
62
+ if (configuration) {
63
+ for (const [paramName, param] of Object.entries(configuration.parameters)) {
62
64
  assertSwiftIdentifier(paramName, 'parameter name');
63
65
  if (param.type === 'number' && typeof param.default !== 'number') {
64
66
  throw new Error(`Invalid default for ${JSON.stringify(paramName)}: must be a number.`);
@@ -154,7 +156,7 @@ struct ExportWidgets${index}: WidgetBundle {
154
156
  var body: some Widget {
155
157
  ${widgets
156
158
  .map((widget) => {
157
- if (widget.configuration) {
159
+ if (widget.ios?.configuration ?? widget.configuration) {
158
160
  return `if #available(iOS 17.0, *) {
159
161
  ${widget.name}()
160
162
  }`;
@@ -166,7 +168,10 @@ struct ExportWidgets${index}: WidgetBundle {
166
168
  }
167
169
  }`;
168
170
  const widgetSwift = (widget) => {
169
- if (!widget.configuration)
171
+ const configuration = widget.ios?.configuration ?? widget.configuration;
172
+ const supportedFamilies = widget.ios?.supportedFamilies ?? widget.supportedFamilies ?? [];
173
+ const contentMarginsDisabled = widget.ios?.contentMarginsDisabled ?? widget.contentMarginsDisabled;
174
+ if (!configuration)
170
175
  return `import WidgetKit
171
176
  import SwiftUI
172
177
  internal import ExpoWidgets
@@ -180,7 +185,7 @@ struct ${widget.name}: Widget {
180
185
  }
181
186
  .configurationDisplayName(${JSON.stringify(widget.displayName)})
182
187
  .description(${JSON.stringify(widget.description)})
183
- .supportedFamilies([.${widget.supportedFamilies.join(', .')}])${widget.contentMarginsDisabled ? '\n .contentMarginsDisabled()' : ''}
188
+ .supportedFamilies([.${supportedFamilies.join(', .')}])${contentMarginsDisabled ? '\n .contentMarginsDisabled()' : ''}
184
189
  }
185
190
  }`;
186
191
  return `import WidgetKit
@@ -190,9 +195,9 @@ internal import ExpoWidgets
190
195
 
191
196
  // AppIntent
192
197
  struct ${widget.name}ConfigurationAppIntent: WidgetConfigurationIntent {
193
- static var title: LocalizedStringResource = ${JSON.stringify(`${widget.configuration?.title ?? widget.displayName} Configuration`)}
194
- ${widget.configuration?.description ? ` static var description: LocalizedStringResource = ${JSON.stringify(widget.configuration.description)}\n` : ''}
195
- ${Object.entries(widget.configuration?.parameters ?? {})
198
+ static var title: LocalizedStringResource = ${JSON.stringify(`${configuration?.title ?? widget.displayName} Configuration`)}
199
+ ${configuration?.description ? ` static var description: LocalizedStringResource = ${JSON.stringify(configuration.description)}\n` : ''}
200
+ ${Object.entries(configuration?.parameters ?? {})
196
201
  .map(([name, param]) => {
197
202
  let paramType;
198
203
  switch (param.type) {
@@ -219,7 +224,7 @@ ${Object.entries(widget.configuration?.parameters ?? {})
219
224
  return .result()
220
225
  }
221
226
  }
222
- ${Object.entries(widget.configuration?.parameters ?? {})
227
+ ${Object.entries(configuration?.parameters ?? {})
223
228
  .map(([name, param]) => {
224
229
  if (param.type !== 'enum')
225
230
  return '';
@@ -300,7 +305,7 @@ struct ${widget.name}EntryView: View {
300
305
  var env: [String: Any] = getWidgetEnvironment(environment: environment)
301
306
  env["timestamp"] = Int(entry.date.timeIntervalSince1970 * 1000)
302
307
  env["configuration"] = [
303
- ${Object.entries(widget.configuration?.parameters ?? {})
308
+ ${Object.entries(configuration?.parameters ?? {})
304
309
  .map(([name, param]) => {
305
310
  return ` "${name}": entry.configuration.${name}${param.type === 'enum' ? '.rawValue' : ''}`;
306
311
  })
@@ -339,7 +344,7 @@ struct ${widget.name}: Widget {
339
344
  }
340
345
  .configurationDisplayName(${JSON.stringify(widget.displayName)})
341
346
  .description(${JSON.stringify(widget.description)})
342
- .supportedFamilies([.${widget.supportedFamilies.join(', .')}])${widget.contentMarginsDisabled ? '\n .contentMarginsDisabled()' : ''}
347
+ .supportedFamilies([.${supportedFamilies.join(', .')}])${contentMarginsDisabled ? '\n .contentMarginsDisabled()' : ''}
343
348
  }
344
349
  }`;
345
350
  };
@@ -1,15 +1,31 @@
1
1
  import { WidgetFamily } from './WidgetFamily.type';
2
2
  export type WidgetConfig = {
3
3
  name: string;
4
- supportedFamilies: WidgetFamily[];
5
4
  displayName: string;
6
5
  description: string;
6
+ supportedFamilies: WidgetFamily[];
7
7
  contentMarginsDisabled: boolean;
8
8
  configuration?: {
9
9
  title: string;
10
10
  description?: string;
11
11
  parameters: Record<string, WidgetParameter>;
12
12
  };
13
+ ios?: {
14
+ supportedFamilies: WidgetFamily[];
15
+ contentMarginsDisabled?: boolean;
16
+ configuration?: {
17
+ title: string;
18
+ description?: string;
19
+ parameters: Record<string, WidgetParameter>;
20
+ };
21
+ } | null;
22
+ android?: {
23
+ minWidth?: number;
24
+ minHeight?: number;
25
+ targetCellWidth?: number;
26
+ targetCellHeight?: number;
27
+ resizeMode?: 'none' | 'horizontal' | 'vertical' | 'both';
28
+ } | null;
13
29
  };
14
30
  export type WidgetParameterString = {
15
31
  title: string;