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.
- package/CHANGELOG.md +16 -0
- package/android/build.gradle +38 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/widgets/ExpoWidgetsAppWidgetProvider.kt +10 -0
- package/android/src/main/java/expo/modules/widgets/ExpoWidgetsGlanceWidget.kt +17 -0
- package/android/src/main/java/expo/modules/widgets/WidgetsModule.kt +10 -0
- package/expo-module.config.json +4 -1
- package/package.json +5 -5
- package/plugin/build/android/resourceNames.d.ts +5 -0
- package/plugin/build/android/resourceNames.js +29 -0
- package/plugin/build/android/withAndroidWidgetFiles.d.ts +4 -0
- package/plugin/build/android/withAndroidWidgetFiles.js +142 -0
- package/plugin/build/android/withAndroidWidgetManifest.d.ts +4 -0
- package/plugin/build/android/withAndroidWidgetManifest.js +58 -0
- package/plugin/build/android/withAndroidWidgets.d.ts +6 -0
- package/plugin/build/android/withAndroidWidgets.js +19 -0
- package/plugin/build/ios/withIosWidgets.d.ts +11 -0
- package/plugin/build/ios/withIosWidgets.js +81 -0
- package/plugin/build/{withWidgetSourceFiles.d.ts → ios/withWidgetSourceFiles.d.ts} +1 -1
- package/plugin/build/{withWidgetSourceFiles.js → ios/withWidgetSourceFiles.js} +18 -13
- package/plugin/build/types/WidgetConfig.type.d.ts +17 -1
- package/plugin/build/types/WidgetFamily.type.d.ts +10 -9
- package/plugin/build/types/WidgetFamily.type.js +9 -10
- package/plugin/build/withWidgets.js +6 -63
- package/plugin/src/android/resourceNames.ts +29 -0
- package/plugin/src/android/withAndroidWidgetFiles.ts +143 -0
- package/plugin/src/android/withAndroidWidgetManifest.ts +94 -0
- package/plugin/src/android/withAndroidWidgets.ts +21 -0
- package/plugin/src/ios/withIosWidgets.ts +99 -0
- package/plugin/src/{withWidgetSourceFiles.ts → ios/withWidgetSourceFiles.ts} +20 -14
- package/plugin/src/types/WidgetConfig.type.ts +20 -1
- package/plugin/src/types/WidgetFamily.type.ts +11 -9
- package/plugin/src/withWidgets.ts +7 -71
- package/plugin/tsconfig.tsbuildinfo +1 -0
- /package/plugin/build/{withAppGroupEntitlements.d.ts → ios/withAppGroupEntitlements.d.ts} +0 -0
- /package/plugin/build/{withAppGroupEntitlements.js → ios/withAppGroupEntitlements.js} +0 -0
- /package/plugin/build/{withAppInfoPlist.d.ts → ios/withAppInfoPlist.d.ts} +0 -0
- /package/plugin/build/{withAppInfoPlist.js → ios/withAppInfoPlist.js} +0 -0
- /package/plugin/build/{withEasConfig.d.ts → ios/withEasConfig.d.ts} +0 -0
- /package/plugin/build/{withEasConfig.js → ios/withEasConfig.js} +0 -0
- /package/plugin/build/{withIosWarning.d.ts → ios/withIosWarning.d.ts} +0 -0
- /package/plugin/build/{withIosWarning.js → ios/withIosWarning.js} +0 -0
- /package/plugin/build/{withPodsLinking.d.ts → ios/withPodsLinking.d.ts} +0 -0
- /package/plugin/build/{withPodsLinking.js → ios/withPodsLinking.js} +0 -0
- /package/plugin/build/{withPushNotifications.d.ts → ios/withPushNotifications.d.ts} +0 -0
- /package/plugin/build/{withPushNotifications.js → ios/withPushNotifications.js} +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addBuildPhases.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addBuildPhases.js +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addPbxGroup.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addPbxGroup.js +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addProductFile.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addProductFile.js +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addTargetDependency.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addTargetDependency.js +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addToPbxNativeTargetSection.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addToPbxNativeTargetSection.js +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addToPbxProjectSection.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addToPbxProjectSection.js +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addXCConfigurationList.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/addXCConfigurationList.js +0 -0
- /package/plugin/build/{xcode → ios/xcode}/withTargetXcodeProject.d.ts +0 -0
- /package/plugin/build/{xcode → ios/xcode}/withTargetXcodeProject.js +0 -0
- /package/plugin/src/{withAppGroupEntitlements.ts → ios/withAppGroupEntitlements.ts} +0 -0
- /package/plugin/src/{withAppInfoPlist.ts → ios/withAppInfoPlist.ts} +0 -0
- /package/plugin/src/{withEasConfig.ts → ios/withEasConfig.ts} +0 -0
- /package/plugin/src/{withIosWarning.ts → ios/withIosWarning.ts} +0 -0
- /package/plugin/src/{withPodsLinking.ts → ios/withPodsLinking.ts} +0 -0
- /package/plugin/src/{withPushNotifications.ts → ios/withPushNotifications.ts} +0 -0
- /package/plugin/src/{xcode → ios/xcode}/addBuildPhases.ts +0 -0
- /package/plugin/src/{xcode → ios/xcode}/addPbxGroup.ts +0 -0
- /package/plugin/src/{xcode → ios/xcode}/addProductFile.ts +0 -0
- /package/plugin/src/{xcode → ios/xcode}/addTargetDependency.ts +0 -0
- /package/plugin/src/{xcode → ios/xcode}/addToPbxNativeTargetSection.ts +0 -0
- /package/plugin/src/{xcode → ios/xcode}/addToPbxProjectSection.ts +0 -0
- /package/plugin/src/{xcode → ios/xcode}/addXCConfigurationList.ts +0 -0
- /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,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
|
+
}
|
package/expo-module.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-widgets",
|
|
3
|
-
"version": "56.0.
|
|
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.
|
|
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.
|
|
34
|
-
"expo
|
|
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": "
|
|
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,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, '&')
|
|
125
|
+
.replace(/"/g, '"')
|
|
126
|
+
.replace(/'/g, ''')
|
|
127
|
+
.replace(/</g, '<')
|
|
128
|
+
.replace(/>/g, '>');
|
|
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,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,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;
|
|
@@ -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("
|
|
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
|
-
|
|
57
|
+
const supportedFamilies = widget.ios?.supportedFamilies ?? widget.supportedFamilies ?? [];
|
|
58
|
+
for (const family of supportedFamilies) {
|
|
58
59
|
assertWidgetFamily(family);
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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([.${
|
|
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(`${
|
|
194
|
-
${
|
|
195
|
-
${Object.entries(
|
|
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(
|
|
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(
|
|
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([.${
|
|
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;
|