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
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
systemSmall
|
|
3
|
-
systemMedium
|
|
4
|
-
systemLarge
|
|
5
|
-
systemExtraLarge
|
|
6
|
-
accessoryCircular
|
|
7
|
-
accessoryRectangular
|
|
8
|
-
accessoryInline
|
|
9
|
-
}
|
|
1
|
+
export declare const WidgetFamily: {
|
|
2
|
+
readonly systemSmall: "systemSmall";
|
|
3
|
+
readonly systemMedium: "systemMedium";
|
|
4
|
+
readonly systemLarge: "systemLarge";
|
|
5
|
+
readonly systemExtraLarge: "systemExtraLarge";
|
|
6
|
+
readonly accessoryCircular: "accessoryCircular";
|
|
7
|
+
readonly accessoryRectangular: "accessoryRectangular";
|
|
8
|
+
readonly accessoryInline: "accessoryInline";
|
|
9
|
+
};
|
|
10
|
+
export type WidgetFamily = (typeof WidgetFamily)[keyof typeof WidgetFamily];
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WidgetFamily = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
})(WidgetFamily || (exports.WidgetFamily = WidgetFamily = {}));
|
|
4
|
+
exports.WidgetFamily = {
|
|
5
|
+
systemSmall: 'systemSmall',
|
|
6
|
+
systemMedium: 'systemMedium',
|
|
7
|
+
systemLarge: 'systemLarge',
|
|
8
|
+
systemExtraLarge: 'systemExtraLarge',
|
|
9
|
+
accessoryCircular: 'accessoryCircular',
|
|
10
|
+
accessoryRectangular: 'accessoryRectangular',
|
|
11
|
+
accessoryInline: 'accessoryInline',
|
|
12
|
+
};
|
|
@@ -4,71 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const config_plugins_1 = require("expo/config-plugins");
|
|
7
|
-
const
|
|
8
|
-
const
|
|
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"));
|
|
7
|
+
const withAndroidWidgets_1 = __importDefault(require("./android/withAndroidWidgets"));
|
|
8
|
+
const withIosWidgets_1 = __importDefault(require("./ios/withIosWidgets"));
|
|
15
9
|
const pkg = require('../../package.json');
|
|
16
10
|
const withWidgets = (config, props) => {
|
|
17
|
-
let plugins = [];
|
|
18
|
-
const deploymentTarget = '16.4';
|
|
19
|
-
const targetName = 'ExpoWidgetsTarget';
|
|
20
|
-
let bundleIdentifier = props?.bundleIdentifier;
|
|
21
|
-
if (!bundleIdentifier) {
|
|
22
|
-
bundleIdentifier = `${config.ios?.bundleIdentifier}.${targetName}`;
|
|
23
|
-
plugins.push([
|
|
24
|
-
withIosWarning_1.default,
|
|
25
|
-
{
|
|
26
|
-
property: 'bundleIdentifier',
|
|
27
|
-
warning: `Expo Widgets: No bundle identifier provided, using fallback: ${bundleIdentifier}.`,
|
|
28
|
-
},
|
|
29
|
-
]);
|
|
30
|
-
}
|
|
31
|
-
let groupIdentifier = props?.groupIdentifier;
|
|
32
|
-
if (!groupIdentifier) {
|
|
33
|
-
if (!config.ios?.bundleIdentifier) {
|
|
34
|
-
throw new Error('iOS bundle identifier is required. Please set `ios.bundleIdentifier` in `app.json` or `app.config.js`');
|
|
35
|
-
}
|
|
36
|
-
groupIdentifier = `group.${config.ios.bundleIdentifier}`;
|
|
37
|
-
plugins.push([
|
|
38
|
-
withIosWarning_1.default,
|
|
39
|
-
{
|
|
40
|
-
property: 'groupIdentifier',
|
|
41
|
-
warning: `Expo Widgets: No group identifier provided, using fallback: ${groupIdentifier}.`,
|
|
42
|
-
},
|
|
43
|
-
]);
|
|
44
|
-
}
|
|
45
11
|
const widgets = props?.widgets ?? [];
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
sharedFiles = [...sharedFiles, ...files];
|
|
51
|
-
};
|
|
52
|
-
const getFileUris = () => sharedFiles;
|
|
53
|
-
plugins = [
|
|
54
|
-
...plugins,
|
|
55
|
-
[withEasConfig_1.default, { targetName, bundleIdentifier, groupIdentifier }],
|
|
56
|
-
[withPodsLinking_1.default, { targetName }],
|
|
57
|
-
[withWidgetSourceFiles_1.default, { targetName, widgets, groupIdentifier, onFilesGenerated: setFiles }],
|
|
58
|
-
[withAppInfoPlist_1.default, { frequentUpdates, groupIdentifier }],
|
|
59
|
-
[withPushNotifications_1.default, { enablePushNotifications }],
|
|
60
|
-
[withAppGroupEntitlements_1.default, { groupIdentifier }],
|
|
61
|
-
[
|
|
62
|
-
withTargetXcodeProject_1.default,
|
|
63
|
-
{
|
|
64
|
-
targetName,
|
|
65
|
-
bundleIdentifier,
|
|
66
|
-
deploymentTarget,
|
|
67
|
-
appleTeamId: config.ios?.appleTeamId,
|
|
68
|
-
getFileUris,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
];
|
|
72
|
-
return (0, config_plugins_1.withPlugins)(config, plugins);
|
|
12
|
+
return (0, config_plugins_1.withPlugins)(config, [
|
|
13
|
+
[withAndroidWidgets_1.default, { widgets }],
|
|
14
|
+
[withIosWidgets_1.default, { ...(props ?? {}), widgets }],
|
|
15
|
+
]);
|
|
73
16
|
};
|
|
74
17
|
exports.default = (0, config_plugins_1.createRunOncePlugin)(withWidgets, pkg.name, pkg.version);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { WidgetConfig } from '../types/WidgetConfig.type';
|
|
2
|
+
|
|
3
|
+
const toAndroidResourceName = (name: string): string => {
|
|
4
|
+
const resourceName = name
|
|
5
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
6
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
7
|
+
.replace(/[^A-Za-z0-9_]/g, '_')
|
|
8
|
+
.replace(/_+/g, '_')
|
|
9
|
+
.replace(/^_+|_+$/g, '')
|
|
10
|
+
.toLowerCase();
|
|
11
|
+
|
|
12
|
+
return /^[a-z]/.test(resourceName) ? resourceName : `widget_${resourceName}`;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getProviderClassName = (widget: WidgetConfig): string => {
|
|
16
|
+
return `${widget.name}Provider`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const getWidgetInfoResourceName = (widget: WidgetConfig): string => {
|
|
20
|
+
return `${toAndroidResourceName(widget.name)}_info`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getWidgetDisplayNameResourceName = (widget: WidgetConfig): string => {
|
|
24
|
+
return `${toAndroidResourceName(widget.name)}_display_name`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const getWidgetDescriptionResourceName = (widget: WidgetConfig): string => {
|
|
28
|
+
return `${toAndroidResourceName(widget.name)}_description`;
|
|
29
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { AndroidConfig, ConfigPlugin, withDangerousMod } from 'expo/config-plugins';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getProviderClassName,
|
|
7
|
+
getWidgetDescriptionResourceName,
|
|
8
|
+
getWidgetDisplayNameResourceName,
|
|
9
|
+
getWidgetInfoResourceName,
|
|
10
|
+
} from './resourceNames';
|
|
11
|
+
import { WidgetConfig } from '../types/WidgetConfig.type';
|
|
12
|
+
|
|
13
|
+
const withAndroidWidgetFiles: ConfigPlugin<WidgetConfig[]> = (config, widgets) => {
|
|
14
|
+
return withDangerousMod(config, [
|
|
15
|
+
'android',
|
|
16
|
+
async (config) => {
|
|
17
|
+
const androidPackage = AndroidConfig.Package.getPackage(config);
|
|
18
|
+
if (
|
|
19
|
+
typeof androidPackage !== 'string' ||
|
|
20
|
+
!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(androidPackage)
|
|
21
|
+
) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'Android package is required for expo-widgets. Please set `android.package` in `app.json` or `app.config.js`.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const projectRoot = config.modRequest.platformProjectRoot;
|
|
28
|
+
const packageDirectory = path.join(
|
|
29
|
+
projectRoot,
|
|
30
|
+
'app/src/main/java',
|
|
31
|
+
...androidPackage.split('.')
|
|
32
|
+
);
|
|
33
|
+
const xmlDirectory = path.join(projectRoot, 'app/src/main/res/xml');
|
|
34
|
+
const valuesDirectory = path.join(projectRoot, 'app/src/main/res/values');
|
|
35
|
+
const widgetsXmlPath = path.join(xmlDirectory, 'expo_widgets.xml');
|
|
36
|
+
|
|
37
|
+
fs.mkdirSync(packageDirectory, { recursive: true });
|
|
38
|
+
fs.mkdirSync(xmlDirectory, { recursive: true });
|
|
39
|
+
fs.mkdirSync(valuesDirectory, { recursive: true });
|
|
40
|
+
|
|
41
|
+
if (fs.existsSync(widgetsXmlPath)) {
|
|
42
|
+
fs.rmSync(widgetsXmlPath);
|
|
43
|
+
}
|
|
44
|
+
fs.writeFileSync(widgetsXmlPath, createWidgetStringsXml(widgets));
|
|
45
|
+
|
|
46
|
+
for (const widget of widgets) {
|
|
47
|
+
const providerPath = path.join(packageDirectory, `${getProviderClassName(widget)}.kt`);
|
|
48
|
+
if (fs.existsSync(providerPath)) {
|
|
49
|
+
fs.rmSync(providerPath);
|
|
50
|
+
}
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
path.join(packageDirectory, `${getProviderClassName(widget)}.kt`),
|
|
53
|
+
createWidgetProviderKt(androidPackage, widget)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const widgetInfoPath = path.join(xmlDirectory, `${getWidgetInfoResourceName(widget)}.xml`);
|
|
57
|
+
if (fs.existsSync(widgetInfoPath)) {
|
|
58
|
+
fs.rmSync(widgetInfoPath);
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
path.join(xmlDirectory, `${getWidgetInfoResourceName(widget)}.xml`),
|
|
62
|
+
createWidgetInfoXml(widget)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return config;
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const createWidgetProviderKt = (androidPackage: string, widget: WidgetConfig): string => {
|
|
72
|
+
return `package ${androidPackage}
|
|
73
|
+
|
|
74
|
+
import expo.modules.widgets.ExpoWidgetsAppWidgetProvider
|
|
75
|
+
|
|
76
|
+
class ${getProviderClassName(widget)} : ExpoWidgetsAppWidgetProvider(${JSON.stringify(widget.name)})
|
|
77
|
+
`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const createWidgetInfoXml = (widget: WidgetConfig): string => {
|
|
81
|
+
const android = getAndroidWidgetConfig(widget);
|
|
82
|
+
|
|
83
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
84
|
+
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
|
85
|
+
android:minWidth="${android.minWidth}dp"
|
|
86
|
+
android:minHeight="${android.minHeight}dp"
|
|
87
|
+
android:targetCellWidth="${android.targetCellWidth}"
|
|
88
|
+
android:targetCellHeight="${android.targetCellHeight}"
|
|
89
|
+
android:updatePeriodMillis="0"
|
|
90
|
+
android:initialLayout="@layout/glance_default_loading_layout"
|
|
91
|
+
android:description="@string/${getWidgetDescriptionResourceName(widget)}"
|
|
92
|
+
android:resizeMode="${getWidgetResizeMode(widget)}"
|
|
93
|
+
android:widgetCategory="home_screen" />
|
|
94
|
+
`;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const getWidgetResizeMode = (widget: WidgetConfig): string => {
|
|
98
|
+
const resizeMode = widget.android?.resizeMode;
|
|
99
|
+
switch (resizeMode) {
|
|
100
|
+
case 'none':
|
|
101
|
+
return 'none';
|
|
102
|
+
case 'horizontal':
|
|
103
|
+
return 'horizontal';
|
|
104
|
+
case 'vertical':
|
|
105
|
+
return 'vertical';
|
|
106
|
+
case 'both':
|
|
107
|
+
default:
|
|
108
|
+
return 'horizontal|vertical';
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getAndroidWidgetConfig = (widget: WidgetConfig): NonNullable<WidgetConfig['android']> => {
|
|
113
|
+
return {
|
|
114
|
+
minWidth: widget.android?.minWidth ?? 180,
|
|
115
|
+
minHeight: widget.android?.minHeight ?? 110,
|
|
116
|
+
targetCellWidth: widget.android?.targetCellWidth ?? 4,
|
|
117
|
+
targetCellHeight: widget.android?.targetCellHeight ?? 2,
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const escapeXmlSpecialChars = (value: string): string => {
|
|
122
|
+
return value
|
|
123
|
+
.replace(/&/g, '&')
|
|
124
|
+
.replace(/"/g, '"')
|
|
125
|
+
.replace(/'/g, ''')
|
|
126
|
+
.replace(/</g, '<')
|
|
127
|
+
.replace(/>/g, '>');
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const createWidgetStringsXml = (widgets: WidgetConfig[]): string => {
|
|
131
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
132
|
+
<resources>
|
|
133
|
+
${widgets
|
|
134
|
+
.map((widget) => {
|
|
135
|
+
return ` <string name="${getWidgetDisplayNameResourceName(widget)}">${escapeXmlSpecialChars(widget.displayName)}</string>
|
|
136
|
+
<string name="${getWidgetDescriptionResourceName(widget)}">${escapeXmlSpecialChars(widget.description)}</string>`;
|
|
137
|
+
})
|
|
138
|
+
.join('\n')}
|
|
139
|
+
</resources>
|
|
140
|
+
`;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export default withAndroidWidgetFiles;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AndroidConfig, ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getProviderClassName,
|
|
5
|
+
getWidgetDisplayNameResourceName,
|
|
6
|
+
getWidgetInfoResourceName,
|
|
7
|
+
} from './resourceNames';
|
|
8
|
+
import { WidgetConfig } from '../types/WidgetConfig.type';
|
|
9
|
+
|
|
10
|
+
const EXPO_WIDGETS_INTERACTION_ACTION = 'expo.modules.widgets.ACTION_WIDGET_INTERACTION';
|
|
11
|
+
const EXPO_WIDGETS_NAME_METADATA = 'expo.modules.widgets.NAME';
|
|
12
|
+
|
|
13
|
+
type ManifestReceiver = {
|
|
14
|
+
$: {
|
|
15
|
+
'android:name': string;
|
|
16
|
+
'android:exported'?: 'true' | 'false';
|
|
17
|
+
'android:label'?: string;
|
|
18
|
+
[key: string]: string | undefined;
|
|
19
|
+
};
|
|
20
|
+
'intent-filter'?: {
|
|
21
|
+
action?: {
|
|
22
|
+
$: {
|
|
23
|
+
'android:name': string;
|
|
24
|
+
};
|
|
25
|
+
}[];
|
|
26
|
+
}[];
|
|
27
|
+
'meta-data'?: {
|
|
28
|
+
$: {
|
|
29
|
+
'android:name': string;
|
|
30
|
+
'android:value'?: string;
|
|
31
|
+
'android:resource'?: string;
|
|
32
|
+
};
|
|
33
|
+
}[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const createWidgetReceiver = (widget: WidgetConfig): ManifestReceiver => {
|
|
37
|
+
return {
|
|
38
|
+
$: {
|
|
39
|
+
'android:name': `.${getProviderClassName(widget)}`,
|
|
40
|
+
'android:exported': 'true',
|
|
41
|
+
'android:label': `@string/${getWidgetDisplayNameResourceName(widget)}`,
|
|
42
|
+
},
|
|
43
|
+
'intent-filter': [
|
|
44
|
+
{
|
|
45
|
+
action: [
|
|
46
|
+
{
|
|
47
|
+
$: {
|
|
48
|
+
'android:name': 'android.appwidget.action.APPWIDGET_UPDATE',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
$: {
|
|
53
|
+
'android:name': EXPO_WIDGETS_INTERACTION_ACTION,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
'meta-data': [
|
|
60
|
+
{
|
|
61
|
+
$: {
|
|
62
|
+
'android:name': 'android.appwidget.provider',
|
|
63
|
+
'android:resource': `@xml/${getWidgetInfoResourceName(widget)}`,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
$: {
|
|
68
|
+
'android:name': EXPO_WIDGETS_NAME_METADATA,
|
|
69
|
+
'android:value': widget.name,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const withAndroidWidgetManifest: ConfigPlugin<WidgetConfig[]> = (config, widgets) => {
|
|
77
|
+
return withAndroidManifest(config, (config) => {
|
|
78
|
+
const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
|
|
79
|
+
const receivers = (mainApplication.receiver ?? []) as ManifestReceiver[];
|
|
80
|
+
const isNotWidgetReceiver = (receiver: ManifestReceiver) =>
|
|
81
|
+
!receiver['meta-data']?.some(
|
|
82
|
+
(metaData) => metaData.$['android:name'] === EXPO_WIDGETS_NAME_METADATA
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
mainApplication.receiver = [
|
|
86
|
+
...receivers.filter(isNotWidgetReceiver),
|
|
87
|
+
...widgets.map(createWidgetReceiver),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
return config;
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default withAndroidWidgetManifest;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
+
|
|
3
|
+
import withAndroidWidgetFiles from './withAndroidWidgetFiles';
|
|
4
|
+
import withAndroidWidgetManifest from './withAndroidWidgetManifest';
|
|
5
|
+
import { WidgetConfig } from '../types/WidgetConfig.type';
|
|
6
|
+
|
|
7
|
+
const withAndroidWidgets: ConfigPlugin<{ widgets: WidgetConfig[] }> = (config, { widgets }) => {
|
|
8
|
+
if (widgets.length === 0) {
|
|
9
|
+
return config;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const androidWidgets = widgets.filter((widget) => widget.android !== null);
|
|
13
|
+
let nextConfig = config;
|
|
14
|
+
if (androidWidgets.length > 0) {
|
|
15
|
+
nextConfig = withAndroidWidgetFiles(nextConfig, androidWidgets);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return withAndroidWidgetManifest(nextConfig, androidWidgets);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default withAndroidWidgets;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ConfigPlugin, StaticPlugin, withPlugins } from 'expo/config-plugins';
|
|
2
|
+
|
|
3
|
+
import withAppGroupEntitlements from './withAppGroupEntitlements';
|
|
4
|
+
import withAppInfoPlist from './withAppInfoPlist';
|
|
5
|
+
import withEasConfig from './withEasConfig';
|
|
6
|
+
import withIosWarning from './withIosWarning';
|
|
7
|
+
import withPodsLinking from './withPodsLinking';
|
|
8
|
+
import withPushNotifications from './withPushNotifications';
|
|
9
|
+
import withWidgetSourceFiles from './withWidgetSourceFiles';
|
|
10
|
+
import { WidgetConfig } from '../types/WidgetConfig.type';
|
|
11
|
+
import withTargetXcodeProject from './xcode/withTargetXcodeProject';
|
|
12
|
+
|
|
13
|
+
type IosWidgetsProps = {
|
|
14
|
+
bundleIdentifier?: string;
|
|
15
|
+
groupIdentifier?: string;
|
|
16
|
+
enablePushNotifications?: boolean;
|
|
17
|
+
frequentUpdates?: boolean;
|
|
18
|
+
widgets: WidgetConfig[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const withIosWidgets: ConfigPlugin<IosWidgetsProps> = (config, props) => {
|
|
22
|
+
const shouldConfigureIos =
|
|
23
|
+
!!config.ios?.bundleIdentifier ||
|
|
24
|
+
!!props.bundleIdentifier ||
|
|
25
|
+
!!props.groupIdentifier ||
|
|
26
|
+
props.enablePushNotifications === true ||
|
|
27
|
+
props.frequentUpdates === true;
|
|
28
|
+
|
|
29
|
+
if (!shouldConfigureIos) {
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let plugins: (StaticPlugin | ConfigPlugin | string)[] = [];
|
|
34
|
+
const deploymentTarget = config.ios?.deploymentTarget ?? '16.4';
|
|
35
|
+
const targetName = 'ExpoWidgetsTarget';
|
|
36
|
+
const widgets = props.widgets.filter((widget) => widget.ios !== null);
|
|
37
|
+
|
|
38
|
+
const enablePushNotifications = props.enablePushNotifications ?? false;
|
|
39
|
+
const frequentUpdates = props.frequentUpdates ?? false;
|
|
40
|
+
|
|
41
|
+
let bundleIdentifier = props.bundleIdentifier;
|
|
42
|
+
if (!bundleIdentifier) {
|
|
43
|
+
bundleIdentifier = `${config.ios?.bundleIdentifier}.${targetName}`;
|
|
44
|
+
plugins.push([
|
|
45
|
+
withIosWarning,
|
|
46
|
+
{
|
|
47
|
+
property: 'bundleIdentifier',
|
|
48
|
+
warning: `Expo Widgets: No bundle identifier provided, using fallback: ${bundleIdentifier}.`,
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let groupIdentifier = props.groupIdentifier;
|
|
54
|
+
if (!groupIdentifier) {
|
|
55
|
+
if (!config.ios?.bundleIdentifier) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
'iOS bundle identifier is required. Please set `ios.bundleIdentifier` in `app.json` or `app.config.js`'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
groupIdentifier = `group.${config.ios.bundleIdentifier}`;
|
|
61
|
+
plugins.push([
|
|
62
|
+
withIosWarning,
|
|
63
|
+
{
|
|
64
|
+
property: 'groupIdentifier',
|
|
65
|
+
warning: `Expo Widgets: No group identifier provided, using fallback: ${groupIdentifier}.`,
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let sharedFiles: string[] = [];
|
|
71
|
+
const setFiles = (files: string[]) => {
|
|
72
|
+
sharedFiles = [...sharedFiles, ...files];
|
|
73
|
+
};
|
|
74
|
+
const getFileUris = () => sharedFiles;
|
|
75
|
+
|
|
76
|
+
plugins = [
|
|
77
|
+
...plugins,
|
|
78
|
+
[withEasConfig, { targetName, bundleIdentifier, groupIdentifier }],
|
|
79
|
+
[withPodsLinking, { targetName }],
|
|
80
|
+
[withWidgetSourceFiles, { targetName, widgets, groupIdentifier, onFilesGenerated: setFiles }],
|
|
81
|
+
[withAppInfoPlist, { frequentUpdates, groupIdentifier }],
|
|
82
|
+
[withPushNotifications, { enablePushNotifications }],
|
|
83
|
+
[withAppGroupEntitlements, { groupIdentifier }],
|
|
84
|
+
[
|
|
85
|
+
withTargetXcodeProject,
|
|
86
|
+
{
|
|
87
|
+
targetName,
|
|
88
|
+
bundleIdentifier,
|
|
89
|
+
deploymentTarget,
|
|
90
|
+
appleTeamId: config.ios?.appleTeamId,
|
|
91
|
+
getFileUris,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
return withPlugins(config, plugins);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default withIosWidgets;
|
|
@@ -3,8 +3,8 @@ import { ConfigPlugin, withDangerousMod } from 'expo/config-plugins';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
|
|
6
|
-
import { WidgetConfig } from '
|
|
7
|
-
import { WidgetFamily } from '
|
|
6
|
+
import { WidgetConfig } from '../types/WidgetConfig.type';
|
|
7
|
+
import { WidgetFamily } from '../types/WidgetFamily.type';
|
|
8
8
|
|
|
9
9
|
type WidgetSourceFilesProps = {
|
|
10
10
|
targetName: string;
|
|
@@ -31,11 +31,13 @@ function assertWidgetFamily(value: unknown): asserts value is WidgetFamily {
|
|
|
31
31
|
|
|
32
32
|
function validateWidget(widget: WidgetConfig): void {
|
|
33
33
|
assertSwiftIdentifier(widget.name, 'widget name');
|
|
34
|
-
|
|
34
|
+
const supportedFamilies = widget.ios?.supportedFamilies ?? widget.supportedFamilies ?? [];
|
|
35
|
+
for (const family of supportedFamilies) {
|
|
35
36
|
assertWidgetFamily(family);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const configuration = widget.ios?.configuration ?? widget.configuration;
|
|
39
|
+
if (configuration) {
|
|
40
|
+
for (const [paramName, param] of Object.entries(configuration.parameters)) {
|
|
39
41
|
assertSwiftIdentifier(paramName, 'parameter name');
|
|
40
42
|
if (param.type === 'number' && typeof param.default !== 'number') {
|
|
41
43
|
throw new Error(`Invalid default for ${JSON.stringify(paramName)}: must be a number.`);
|
|
@@ -161,7 +163,7 @@ struct ExportWidgets${index}: WidgetBundle {
|
|
|
161
163
|
var body: some Widget {
|
|
162
164
|
${widgets
|
|
163
165
|
.map((widget) => {
|
|
164
|
-
if (widget.configuration) {
|
|
166
|
+
if (widget.ios?.configuration ?? widget.configuration) {
|
|
165
167
|
return `if #available(iOS 17.0, *) {
|
|
166
168
|
${widget.name}()
|
|
167
169
|
}`;
|
|
@@ -174,7 +176,11 @@ struct ExportWidgets${index}: WidgetBundle {
|
|
|
174
176
|
}`;
|
|
175
177
|
|
|
176
178
|
const widgetSwift = (widget: WidgetConfig): string => {
|
|
177
|
-
|
|
179
|
+
const configuration = widget.ios?.configuration ?? widget.configuration;
|
|
180
|
+
const supportedFamilies = widget.ios?.supportedFamilies ?? widget.supportedFamilies ?? [];
|
|
181
|
+
const contentMarginsDisabled =
|
|
182
|
+
widget.ios?.contentMarginsDisabled ?? widget.contentMarginsDisabled;
|
|
183
|
+
if (!configuration)
|
|
178
184
|
return `import WidgetKit
|
|
179
185
|
import SwiftUI
|
|
180
186
|
internal import ExpoWidgets
|
|
@@ -188,7 +194,7 @@ struct ${widget.name}: Widget {
|
|
|
188
194
|
}
|
|
189
195
|
.configurationDisplayName(${JSON.stringify(widget.displayName)})
|
|
190
196
|
.description(${JSON.stringify(widget.description)})
|
|
191
|
-
.supportedFamilies([.${
|
|
197
|
+
.supportedFamilies([.${supportedFamilies.join(', .')}])${contentMarginsDisabled ? '\n .contentMarginsDisabled()' : ''}
|
|
192
198
|
}
|
|
193
199
|
}`;
|
|
194
200
|
return `import WidgetKit
|
|
@@ -198,9 +204,9 @@ internal import ExpoWidgets
|
|
|
198
204
|
|
|
199
205
|
// AppIntent
|
|
200
206
|
struct ${widget.name}ConfigurationAppIntent: WidgetConfigurationIntent {
|
|
201
|
-
static var title: LocalizedStringResource = ${JSON.stringify(`${
|
|
202
|
-
${
|
|
203
|
-
${Object.entries(
|
|
207
|
+
static var title: LocalizedStringResource = ${JSON.stringify(`${configuration?.title ?? widget.displayName} Configuration`)}
|
|
208
|
+
${configuration?.description ? ` static var description: LocalizedStringResource = ${JSON.stringify(configuration.description)}\n` : ''}
|
|
209
|
+
${Object.entries(configuration?.parameters ?? {})
|
|
204
210
|
.map(([name, param]) => {
|
|
205
211
|
let paramType: string;
|
|
206
212
|
switch (param.type) {
|
|
@@ -227,7 +233,7 @@ ${Object.entries(widget.configuration?.parameters ?? {})
|
|
|
227
233
|
return .result()
|
|
228
234
|
}
|
|
229
235
|
}
|
|
230
|
-
${Object.entries(
|
|
236
|
+
${Object.entries(configuration?.parameters ?? {})
|
|
231
237
|
.map(([name, param]) => {
|
|
232
238
|
if (param.type !== 'enum') return '';
|
|
233
239
|
const paramTypeName = `${widget.name}${name[0]?.toUpperCase() + name.slice(1)}Enum`;
|
|
@@ -307,7 +313,7 @@ struct ${widget.name}EntryView: View {
|
|
|
307
313
|
var env: [String: Any] = getWidgetEnvironment(environment: environment)
|
|
308
314
|
env["timestamp"] = Int(entry.date.timeIntervalSince1970 * 1000)
|
|
309
315
|
env["configuration"] = [
|
|
310
|
-
${Object.entries(
|
|
316
|
+
${Object.entries(configuration?.parameters ?? {})
|
|
311
317
|
.map(([name, param]) => {
|
|
312
318
|
return ` "${name}": entry.configuration.${name}${param.type === 'enum' ? '.rawValue' : ''}`;
|
|
313
319
|
})
|
|
@@ -346,7 +352,7 @@ struct ${widget.name}: Widget {
|
|
|
346
352
|
}
|
|
347
353
|
.configurationDisplayName(${JSON.stringify(widget.displayName)})
|
|
348
354
|
.description(${JSON.stringify(widget.description)})
|
|
349
|
-
.supportedFamilies([.${
|
|
355
|
+
.supportedFamilies([.${supportedFamilies.join(', .')}])${contentMarginsDisabled ? '\n .contentMarginsDisabled()' : ''}
|
|
350
356
|
}
|
|
351
357
|
}`;
|
|
352
358
|
};
|
|
@@ -2,15 +2,34 @@ import { WidgetFamily } from './WidgetFamily.type';
|
|
|
2
2
|
|
|
3
3
|
export type WidgetConfig = {
|
|
4
4
|
name: string;
|
|
5
|
-
supportedFamilies: WidgetFamily[];
|
|
6
5
|
displayName: string;
|
|
7
6
|
description: string;
|
|
7
|
+
// @deprecated: use `ios.supportedFamilies` instead.
|
|
8
|
+
supportedFamilies: WidgetFamily[];
|
|
9
|
+
// @deprecated: use `ios.contentMarginsDisabled` instead.
|
|
8
10
|
contentMarginsDisabled: boolean;
|
|
11
|
+
// @deprecated: use `ios.configuration` instead.
|
|
9
12
|
configuration?: {
|
|
10
13
|
title: string;
|
|
11
14
|
description?: string;
|
|
12
15
|
parameters: Record<string, WidgetParameter>;
|
|
13
16
|
};
|
|
17
|
+
ios?: {
|
|
18
|
+
supportedFamilies: WidgetFamily[];
|
|
19
|
+
contentMarginsDisabled?: boolean;
|
|
20
|
+
configuration?: {
|
|
21
|
+
title: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
parameters: Record<string, WidgetParameter>;
|
|
24
|
+
};
|
|
25
|
+
} | null;
|
|
26
|
+
android?: {
|
|
27
|
+
minWidth?: number;
|
|
28
|
+
minHeight?: number;
|
|
29
|
+
targetCellWidth?: number;
|
|
30
|
+
targetCellHeight?: number;
|
|
31
|
+
resizeMode?: 'none' | 'horizontal' | 'vertical' | 'both';
|
|
32
|
+
} | null;
|
|
14
33
|
};
|
|
15
34
|
|
|
16
35
|
export type WidgetParameterString = {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export
|
|
2
|
-
systemSmall
|
|
3
|
-
systemMedium
|
|
4
|
-
systemLarge
|
|
5
|
-
systemExtraLarge
|
|
6
|
-
accessoryCircular
|
|
7
|
-
accessoryRectangular
|
|
8
|
-
accessoryInline
|
|
9
|
-
}
|
|
1
|
+
export const WidgetFamily = {
|
|
2
|
+
systemSmall: 'systemSmall',
|
|
3
|
+
systemMedium: 'systemMedium',
|
|
4
|
+
systemLarge: 'systemLarge',
|
|
5
|
+
systemExtraLarge: 'systemExtraLarge',
|
|
6
|
+
accessoryCircular: 'accessoryCircular',
|
|
7
|
+
accessoryRectangular: 'accessoryRectangular',
|
|
8
|
+
accessoryInline: 'accessoryInline',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type WidgetFamily = (typeof WidgetFamily)[keyof typeof WidgetFamily];
|