react-native-icon-switcher 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/IconSwitcher.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +252 -2
- package/android/build.gradle +51 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcher.kt +380 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcherExceptions.kt +24 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcherModule.kt +67 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcherPackage.kt +30 -0
- package/android/src/test/AndroidManifest.xml +39 -0
- package/android/src/test/java/com/iconswitcher/IconSwitcherTest.kt +392 -0
- package/app.plugin.js +1 -0
- package/ios/ISIconSwitcher.h +49 -0
- package/ios/ISIconSwitcher.m +205 -0
- package/ios/IconSwitcher.h +5 -0
- package/ios/IconSwitcher.mm +100 -0
- package/jest/index.d.ts +31 -0
- package/jest/index.js +98 -0
- package/lib/module/NativeIconSwitcher.js +5 -0
- package/lib/module/NativeIconSwitcher.js.map +1 -0
- package/lib/module/config.js +6 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/errors.js +28 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +69 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/register.js +2 -0
- package/lib/module/register.js.map +1 -0
- package/lib/module/remote/index.js +119 -0
- package/lib/module/remote/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/typetests/icon-name.typetest.js +8 -0
- package/lib/module/typetests/icon-name.typetest.js.map +1 -0
- package/lib/module/typetests/types-in-sync.typetest.js +7 -0
- package/lib/module/typetests/types-in-sync.typetest.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeIconSwitcher.d.ts +18 -0
- package/lib/typescript/src/NativeIconSwitcher.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +4 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +7 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +20 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/register.d.ts +7 -0
- package/lib/typescript/src/register.d.ts.map +1 -0
- package/lib/typescript/src/remote/index.d.ts +17 -0
- package/lib/typescript/src/remote/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +59 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +251 -4
- package/plugin/build/cli.d.ts +3 -0
- package/plugin/build/cli.js +89 -0
- package/plugin/build/core/assets.d.ts +80 -0
- package/plugin/build/core/assets.js +463 -0
- package/plugin/build/core/errors.d.ts +4 -0
- package/plugin/build/core/errors.js +11 -0
- package/plugin/build/core/validate.d.ts +6 -0
- package/plugin/build/core/validate.js +229 -0
- package/plugin/build/index.d.ts +6 -0
- package/plugin/build/index.js +134 -0
- package/plugin/build/load-config.d.ts +3 -0
- package/plugin/build/load-config.js +50 -0
- package/plugin/build/paths.d.ts +15 -0
- package/plugin/build/paths.js +153 -0
- package/plugin/build/sync.d.ts +12 -0
- package/plugin/build/sync.js +162 -0
- package/plugin/build/transforms/android.d.ts +5 -0
- package/plugin/build/transforms/android.js +212 -0
- package/plugin/build/transforms/errors.d.ts +4 -0
- package/plugin/build/transforms/errors.js +11 -0
- package/plugin/build/transforms/index.d.ts +4 -0
- package/plugin/build/transforms/index.js +12 -0
- package/plugin/build/transforms/ios.d.ts +4 -0
- package/plugin/build/transforms/ios.js +111 -0
- package/plugin/build/transforms/sampleConfigs.d.ts +3 -0
- package/plugin/build/transforms/sampleConfigs.js +33 -0
- package/plugin/build/transforms/types.d.ts +2 -0
- package/plugin/build/transforms/types.js +14 -0
- package/plugin/build/types.d.ts +61 -0
- package/plugin/build/types.js +5 -0
- package/src/NativeIconSwitcher.ts +17 -0
- package/src/config.ts +15 -0
- package/src/errors.ts +38 -0
- package/src/index.tsx +78 -0
- package/src/register.ts +4 -0
- package/src/remote/index.ts +142 -0
- package/src/types.ts +59 -0
- package/src/typetests/icon-name.typetest.ts +12 -0
- package/src/typetests/types-in-sync.typetest.ts +16 -0
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
exports.ProjectLayoutError = void 0;
|
|
7
|
+
exports.syncProject = syncProject;
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
12
|
+
const plist_1 = __importDefault(require("@expo/plist"));
|
|
13
|
+
const xcode_1 = __importDefault(require("xcode"));
|
|
14
|
+
const assets_1 = require("./core/assets");
|
|
15
|
+
const validate_1 = require("./core/validate");
|
|
16
|
+
const android_1 = require("./transforms/android");
|
|
17
|
+
const ios_1 = require("./transforms/ios");
|
|
18
|
+
const types_1 = require("./transforms/types");
|
|
19
|
+
const load_config_1 = require("./load-config");
|
|
20
|
+
const paths_1 = require("./paths");
|
|
21
|
+
var paths_2 = require("./paths");
|
|
22
|
+
Object.defineProperty(exports, "ProjectLayoutError", { enumerable: true, get: function () { return paths_2.ProjectLayoutError; } });
|
|
23
|
+
async function _readFileIfExists(abs) {
|
|
24
|
+
try {
|
|
25
|
+
return await promises_1.default.readFile(abs);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function _rel(projectRoot, abs) {
|
|
32
|
+
return node_path_1.default.relative(projectRoot, abs).split(node_path_1.default.sep).join('/');
|
|
33
|
+
}
|
|
34
|
+
async function _applyFileChange(_projectRoot, abs, next, check) {
|
|
35
|
+
const nextBuf = typeof next === 'string' ? Buffer.from(next, 'utf8') : next;
|
|
36
|
+
const existing = await _readFileIfExists(abs);
|
|
37
|
+
if (existing !== null && existing.equals(nextBuf)) {
|
|
38
|
+
return 'unchanged';
|
|
39
|
+
}
|
|
40
|
+
if (check) {
|
|
41
|
+
return 'updated';
|
|
42
|
+
}
|
|
43
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(abs), { recursive: true });
|
|
44
|
+
await promises_1.default.writeFile(abs, nextBuf);
|
|
45
|
+
return 'updated';
|
|
46
|
+
}
|
|
47
|
+
function _recordOutcome(projectRoot, abs, outcome, driftedPaths) {
|
|
48
|
+
if (outcome === 'updated') {
|
|
49
|
+
driftedPaths.push(_rel(projectRoot, abs));
|
|
50
|
+
return 'updated';
|
|
51
|
+
}
|
|
52
|
+
return 'unchanged';
|
|
53
|
+
}
|
|
54
|
+
async function _syncAssets(resolved, projectRoot, iosAssetCatalog, androidResDir, check, driftedPaths) {
|
|
55
|
+
const plan = (0, assets_1.planAssets)(resolved, projectRoot);
|
|
56
|
+
const tmpRoot = await promises_1.default.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'icon-switcher-sync-'));
|
|
57
|
+
const tmpIos = node_path_1.default.join(tmpRoot, 'ios');
|
|
58
|
+
const tmpAndroid = node_path_1.default.join(tmpRoot, 'android');
|
|
59
|
+
let updated = 0;
|
|
60
|
+
let unchanged = 0;
|
|
61
|
+
try {
|
|
62
|
+
await (0, assets_1.writeAssets)(plan, {
|
|
63
|
+
iosAssetCatalog: tmpIos,
|
|
64
|
+
androidResDir: tmpAndroid,
|
|
65
|
+
});
|
|
66
|
+
for (const asset of plan.assets) {
|
|
67
|
+
const isIos = asset.relativePath.includes('.appiconset');
|
|
68
|
+
const targetAbs = node_path_1.default.join(isIos ? iosAssetCatalog : androidResDir, asset.relativePath);
|
|
69
|
+
const stagedAbs = node_path_1.default.join(isIos ? tmpIos : tmpAndroid, asset.relativePath);
|
|
70
|
+
const staged = await promises_1.default.readFile(stagedAbs);
|
|
71
|
+
const outcome = await _applyFileChange(projectRoot, targetAbs, staged, check);
|
|
72
|
+
if (_recordOutcome(projectRoot, targetAbs, outcome, driftedPaths) ===
|
|
73
|
+
'updated') {
|
|
74
|
+
updated += 1;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
unchanged += 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await promises_1.default.rm(tmpRoot, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
return { updated, unchanged };
|
|
85
|
+
}
|
|
86
|
+
async function _syncAndroidManifest(resolved, manifestPath, projectRoot, check, driftedPaths) {
|
|
87
|
+
const xml = await promises_1.default.readFile(manifestPath, 'utf8');
|
|
88
|
+
const parsed = (await config_plugins_1.XML.parseXMLAsync(xml));
|
|
89
|
+
const next = (0, android_1.applyAndroidManifest)(parsed, resolved);
|
|
90
|
+
const nextXml = config_plugins_1.XML.format(next);
|
|
91
|
+
const nextBuf = Buffer.from(nextXml, 'utf8');
|
|
92
|
+
const existing = await _readFileIfExists(manifestPath);
|
|
93
|
+
if (existing !== null && existing.equals(nextBuf)) {
|
|
94
|
+
return _recordOutcome(projectRoot, manifestPath, 'unchanged', driftedPaths);
|
|
95
|
+
}
|
|
96
|
+
if (!check) {
|
|
97
|
+
await config_plugins_1.AndroidConfig.Manifest.writeAndroidManifestAsync(manifestPath, next);
|
|
98
|
+
}
|
|
99
|
+
return _recordOutcome(projectRoot, manifestPath, 'updated', driftedPaths);
|
|
100
|
+
}
|
|
101
|
+
async function _syncInfoPlist(resolved, infoPlistPath, projectRoot, check, driftedPaths) {
|
|
102
|
+
const raw = await promises_1.default.readFile(infoPlistPath, 'utf8');
|
|
103
|
+
const parsed = plist_1.default.parse(raw);
|
|
104
|
+
const next = (0, ios_1.applyInfoPlist)(parsed, resolved);
|
|
105
|
+
const nextXml = plist_1.default.build(next);
|
|
106
|
+
const outcome = await _applyFileChange(projectRoot, infoPlistPath, nextXml, check);
|
|
107
|
+
return _recordOutcome(projectRoot, infoPlistPath, outcome, driftedPaths);
|
|
108
|
+
}
|
|
109
|
+
async function _syncXcodeProject(resolved, pbxprojPath, projectRoot, check, driftedPaths) {
|
|
110
|
+
const project = xcode_1.default.project(pbxprojPath);
|
|
111
|
+
project.parseSync();
|
|
112
|
+
(0, ios_1.applyXcodeProject)(project, resolved);
|
|
113
|
+
const nextContent = project.writeSync();
|
|
114
|
+
const outcome = await _applyFileChange(projectRoot, pbxprojPath, nextContent, check);
|
|
115
|
+
return _recordOutcome(projectRoot, pbxprojPath, outcome, driftedPaths);
|
|
116
|
+
}
|
|
117
|
+
async function _syncTypes(resolved, projectRoot, check, driftedPaths) {
|
|
118
|
+
const typesPath = node_path_1.default.join(projectRoot, 'icon-switcher.d.ts');
|
|
119
|
+
const content = (0, types_1.renderTypes)(resolved);
|
|
120
|
+
const outcome = await _applyFileChange(projectRoot, typesPath, content, check);
|
|
121
|
+
return _recordOutcome(projectRoot, typesPath, outcome, driftedPaths);
|
|
122
|
+
}
|
|
123
|
+
async function syncProject(opts) {
|
|
124
|
+
const projectRoot = node_path_1.default.resolve(opts.projectRoot);
|
|
125
|
+
const configPath = (0, load_config_1.resolveConfigPath)(projectRoot, opts.configPath);
|
|
126
|
+
const rawConfig = (0, load_config_1.loadIconSwitcherConfig)(configPath);
|
|
127
|
+
const resolved = (0, validate_1.resolveConfig)(rawConfig, { projectRoot });
|
|
128
|
+
const paths = (0, paths_1.locateNativePaths)(projectRoot);
|
|
129
|
+
const driftedPaths = [];
|
|
130
|
+
let updated = 0;
|
|
131
|
+
let unchanged = 0;
|
|
132
|
+
const assets = await _syncAssets(resolved, projectRoot, paths.iosAssetCatalog, paths.androidResDir, opts.check, driftedPaths);
|
|
133
|
+
updated += assets.updated;
|
|
134
|
+
unchanged += assets.unchanged;
|
|
135
|
+
const nativeFiles = [
|
|
136
|
+
{
|
|
137
|
+
abs: paths.manifestPath,
|
|
138
|
+
outcome: await _syncAndroidManifest(resolved, paths.manifestPath, projectRoot, opts.check, driftedPaths),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
abs: paths.infoPlistPath,
|
|
142
|
+
outcome: await _syncInfoPlist(resolved, paths.infoPlistPath, projectRoot, opts.check, driftedPaths),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
abs: paths.pbxprojPath,
|
|
146
|
+
outcome: await _syncXcodeProject(resolved, paths.pbxprojPath, projectRoot, opts.check, driftedPaths),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
abs: node_path_1.default.join(projectRoot, 'icon-switcher.d.ts'),
|
|
150
|
+
outcome: await _syncTypes(resolved, projectRoot, opts.check, driftedPaths),
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
for (const file of nativeFiles) {
|
|
154
|
+
if (file.outcome === 'updated') {
|
|
155
|
+
updated += 1;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
unchanged += 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { updated, unchanged, driftedPaths: [...new Set(driftedPaths)] };
|
|
162
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AndroidConfig } from '@expo/config-plugins';
|
|
2
|
+
import type { ResolvedConfig } from '../types';
|
|
3
|
+
type AndroidManifest = AndroidConfig.Manifest.AndroidManifest;
|
|
4
|
+
export declare function applyAndroidManifest(manifest: AndroidManifest, cfg: ResolvedConfig): AndroidManifest;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyAndroidManifest = applyAndroidManifest;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const MAIN_ACTION = 'android.intent.action.MAIN';
|
|
6
|
+
const LAUNCHER_CATEGORY = 'android.intent.category.LAUNCHER';
|
|
7
|
+
const ICONKIT_ICON_META = 'com.iconswitcher.icon';
|
|
8
|
+
const ICONKIT_ICONS_META = 'com.iconswitcher.icons';
|
|
9
|
+
const ICONKIT_APPLY_ON_META = 'com.iconswitcher.applyOn';
|
|
10
|
+
const ICONKIT_SKIP_BRANDS_META = 'com.iconswitcher.skipBrands';
|
|
11
|
+
const DEFAULT_ALIAS = '.IconSwitcher_default';
|
|
12
|
+
const RECOGNIZED_ACTIONS = new Set([MAIN_ACTION]);
|
|
13
|
+
const RECOGNIZED_CATEGORIES = new Set([LAUNCHER_CATEGORY]);
|
|
14
|
+
function applyAndroidManifest(manifest, cfg) {
|
|
15
|
+
const next = JSON.parse(JSON.stringify(manifest));
|
|
16
|
+
const application = _getApplication(next);
|
|
17
|
+
const launcherActivityName = _resolveLauncherActivityName(application);
|
|
18
|
+
_stripLauncherFilters(application, launcherActivityName);
|
|
19
|
+
_syncActivityAliases(application, cfg, launcherActivityName);
|
|
20
|
+
_syncApplicationMetaData(application, cfg);
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
function _getApplication(manifest) {
|
|
24
|
+
var _a;
|
|
25
|
+
const application = (_a = manifest.manifest.application) === null || _a === void 0 ? void 0 : _a[0];
|
|
26
|
+
if (!application) {
|
|
27
|
+
throw new errors_1.TransformError('AndroidManifest has no <application> element');
|
|
28
|
+
}
|
|
29
|
+
return application;
|
|
30
|
+
}
|
|
31
|
+
function _resolveLauncherActivityName(application) {
|
|
32
|
+
var _a, _b, _c, _d, _e, _f;
|
|
33
|
+
const withLauncher = ((_a = application.activity) !== null && _a !== void 0 ? _a : []).find((activity) => { var _a; return ((_a = activity['intent-filter']) !== null && _a !== void 0 ? _a : []).some(_isLauncherIntentFilter); });
|
|
34
|
+
if ((_b = withLauncher === null || withLauncher === void 0 ? void 0 : withLauncher.$) === null || _b === void 0 ? void 0 : _b['android:name']) {
|
|
35
|
+
return withLauncher.$['android:name'];
|
|
36
|
+
}
|
|
37
|
+
const mainActivity = ((_c = application.activity) !== null && _c !== void 0 ? _c : []).find((activity) => { var _a; return ((_a = activity.$) === null || _a === void 0 ? void 0 : _a['android:name']) === '.MainActivity'; });
|
|
38
|
+
if ((_d = mainActivity === null || mainActivity === void 0 ? void 0 : mainActivity.$) === null || _d === void 0 ? void 0 : _d['android:name']) {
|
|
39
|
+
return mainActivity.$['android:name'];
|
|
40
|
+
}
|
|
41
|
+
const defaultAlias = ((_e = application['activity-alias']) !== null && _e !== void 0 ? _e : []).find((alias) => { var _a; return ((_a = alias.$) === null || _a === void 0 ? void 0 : _a['android:name']) === DEFAULT_ALIAS; });
|
|
42
|
+
const target = (_f = defaultAlias === null || defaultAlias === void 0 ? void 0 : defaultAlias.$) === null || _f === void 0 ? void 0 : _f['android:targetActivity'];
|
|
43
|
+
if (target) {
|
|
44
|
+
return target;
|
|
45
|
+
}
|
|
46
|
+
throw new errors_1.TransformError('Could not find launcher activity (no MAIN/LAUNCHER filter, .MainActivity, or default activity-alias)');
|
|
47
|
+
}
|
|
48
|
+
function _stripLauncherFilters(application, activityName) {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
const activities = (_a = application.activity) !== null && _a !== void 0 ? _a : [];
|
|
51
|
+
const activity = activities.find((item) => { var _a; return ((_a = item.$) === null || _a === void 0 ? void 0 : _a['android:name']) === activityName; });
|
|
52
|
+
if (!activity) {
|
|
53
|
+
throw new errors_1.TransformError(`Launcher activity ${activityName} not found in manifest`);
|
|
54
|
+
}
|
|
55
|
+
const filters = (_b = activity['intent-filter']) !== null && _b !== void 0 ? _b : [];
|
|
56
|
+
activity['intent-filter'] = filters.filter((filter) => {
|
|
57
|
+
if (!_isLauncherIntentFilter(filter)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
_assertRecognizedLauncherFilter(filter);
|
|
61
|
+
return false;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function _isLauncherIntentFilter(filter) {
|
|
65
|
+
const actions = _actionNames(filter);
|
|
66
|
+
const categories = _categoryNames(filter);
|
|
67
|
+
return (actions.includes(MAIN_ACTION) && categories.includes(LAUNCHER_CATEGORY));
|
|
68
|
+
}
|
|
69
|
+
function _assertRecognizedLauncherFilter(filter) {
|
|
70
|
+
const actions = _actionNames(filter);
|
|
71
|
+
const categories = _categoryNames(filter);
|
|
72
|
+
for (const action of actions) {
|
|
73
|
+
if (!RECOGNIZED_ACTIONS.has(action)) {
|
|
74
|
+
throw new errors_1.TransformError(`Unrecognized action in launcher intent-filter: ${action}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const category of categories) {
|
|
78
|
+
if (!RECOGNIZED_CATEGORIES.has(category)) {
|
|
79
|
+
throw new errors_1.TransformError(`Unrecognized category in launcher intent-filter: ${category}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function _actionNames(filter) {
|
|
84
|
+
var _a;
|
|
85
|
+
return ((_a = filter.action) !== null && _a !== void 0 ? _a : [])
|
|
86
|
+
.map((item) => { var _a; return (_a = item.$) === null || _a === void 0 ? void 0 : _a['android:name']; })
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
function _categoryNames(filter) {
|
|
90
|
+
var _a;
|
|
91
|
+
return ((_a = filter.category) !== null && _a !== void 0 ? _a : [])
|
|
92
|
+
.map((item) => { var _a; return (_a = item.$) === null || _a === void 0 ? void 0 : _a['android:name']; })
|
|
93
|
+
.filter(Boolean);
|
|
94
|
+
}
|
|
95
|
+
function _syncActivityAliases(application, cfg, launcherActivityName) {
|
|
96
|
+
var _a;
|
|
97
|
+
const configuredNames = new Set(cfg.icons
|
|
98
|
+
.filter((icon) => icon.isDefault || icon.android !== null)
|
|
99
|
+
.map((icon) => icon.name));
|
|
100
|
+
const existing = (_a = application['activity-alias']) !== null && _a !== void 0 ? _a : [];
|
|
101
|
+
const kept = [];
|
|
102
|
+
for (const alias of existing) {
|
|
103
|
+
const marker = _readIconKitMarker(alias);
|
|
104
|
+
if (marker === null) {
|
|
105
|
+
kept.push(alias);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!configuredNames.has(marker)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
kept.push(_buildActivityAlias(marker, cfg, launcherActivityName, alias));
|
|
112
|
+
}
|
|
113
|
+
for (const icon of cfg.icons) {
|
|
114
|
+
if (!icon.isDefault && icon.android === null) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const aliasName = `.IconSwitcher_${icon.name}`;
|
|
118
|
+
const already = kept.some((alias) => { var _a; return ((_a = alias.$) === null || _a === void 0 ? void 0 : _a['android:name']) === aliasName; });
|
|
119
|
+
if (!already) {
|
|
120
|
+
kept.push(_buildActivityAlias(icon.name, cfg, launcherActivityName));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
application['activity-alias'] = kept;
|
|
124
|
+
}
|
|
125
|
+
function _buildActivityAlias(iconName, cfg, launcherActivityName, previous) {
|
|
126
|
+
var _a, _b;
|
|
127
|
+
const icon = cfg.icons.find((item) => item.name === iconName);
|
|
128
|
+
const isDefault = (_a = icon === null || icon === void 0 ? void 0 : icon.isDefault) !== null && _a !== void 0 ? _a : iconName === 'default';
|
|
129
|
+
const { icon: iconRef, roundIcon } = _mipmapRefs(iconName, isDefault);
|
|
130
|
+
const aliasAttrs = {
|
|
131
|
+
...((_b = previous === null || previous === void 0 ? void 0 : previous.$) !== null && _b !== void 0 ? _b : {}),
|
|
132
|
+
'android:name': `.IconSwitcher_${iconName}`,
|
|
133
|
+
'android:targetActivity': launcherActivityName,
|
|
134
|
+
'android:exported': 'true',
|
|
135
|
+
'android:enabled': isDefault ? 'true' : 'false',
|
|
136
|
+
'android:icon': iconRef,
|
|
137
|
+
'android:roundIcon': roundIcon,
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
'$': aliasAttrs,
|
|
141
|
+
'intent-filter': [_launcherIntentFilter()],
|
|
142
|
+
'meta-data': [_iconKitMarkerMeta(iconName)],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function _mipmapRefs(iconName, isDefault) {
|
|
146
|
+
if (isDefault) {
|
|
147
|
+
return {
|
|
148
|
+
icon: '@mipmap/ic_launcher',
|
|
149
|
+
roundIcon: '@mipmap/ic_launcher_round',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
icon: `@mipmap/ic_launcher_${iconName}`,
|
|
154
|
+
roundIcon: `@mipmap/ic_launcher_${iconName}_round`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function _launcherIntentFilter() {
|
|
158
|
+
return {
|
|
159
|
+
action: [{ $: { 'android:name': MAIN_ACTION } }],
|
|
160
|
+
category: [{ $: { 'android:name': LAUNCHER_CATEGORY } }],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function _iconKitMarkerMeta(iconName) {
|
|
164
|
+
return {
|
|
165
|
+
$: {
|
|
166
|
+
'android:name': ICONKIT_ICON_META,
|
|
167
|
+
'android:value': iconName,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function _readIconKitMarker(alias) {
|
|
172
|
+
var _a, _b, _c;
|
|
173
|
+
const meta = (_a = alias['meta-data']) !== null && _a !== void 0 ? _a : [];
|
|
174
|
+
for (const item of meta) {
|
|
175
|
+
if (((_b = item.$) === null || _b === void 0 ? void 0 : _b['android:name']) === ICONKIT_ICON_META) {
|
|
176
|
+
return (_c = item.$['android:value']) !== null && _c !== void 0 ? _c : null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
function _syncApplicationMetaData(application, cfg) {
|
|
182
|
+
var _a;
|
|
183
|
+
const meta = (_a = application['meta-data']) !== null && _a !== void 0 ? _a : [];
|
|
184
|
+
const withoutIconKit = meta.filter((item) => {
|
|
185
|
+
var _a, _b, _c;
|
|
186
|
+
return ((_a = item.$) === null || _a === void 0 ? void 0 : _a['android:name']) !== ICONKIT_ICONS_META &&
|
|
187
|
+
((_b = item.$) === null || _b === void 0 ? void 0 : _b['android:name']) !== ICONKIT_APPLY_ON_META &&
|
|
188
|
+
((_c = item.$) === null || _c === void 0 ? void 0 : _c['android:name']) !== ICONKIT_SKIP_BRANDS_META;
|
|
189
|
+
});
|
|
190
|
+
withoutIconKit.push({
|
|
191
|
+
$: {
|
|
192
|
+
'android:name': ICONKIT_ICONS_META,
|
|
193
|
+
'android:value': _configuredAndroidIconNames(cfg).join(','),
|
|
194
|
+
},
|
|
195
|
+
}, {
|
|
196
|
+
$: {
|
|
197
|
+
'android:name': ICONKIT_APPLY_ON_META,
|
|
198
|
+
'android:value': cfg.android.applyOn,
|
|
199
|
+
},
|
|
200
|
+
}, {
|
|
201
|
+
$: {
|
|
202
|
+
'android:name': ICONKIT_SKIP_BRANDS_META,
|
|
203
|
+
'android:value': cfg.android.skipBrands.join(','),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
application['meta-data'] = withoutIconKit;
|
|
207
|
+
}
|
|
208
|
+
function _configuredAndroidIconNames(cfg) {
|
|
209
|
+
return cfg.icons
|
|
210
|
+
.filter((icon) => icon.isDefault || icon.android !== null)
|
|
211
|
+
.map((icon) => icon.name);
|
|
212
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransformError = void 0;
|
|
4
|
+
class TransformError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'TransformError';
|
|
8
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.TransformError = TransformError;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderTypes = exports.applyXcodeProject = exports.applyInfoPlist = exports.TransformError = exports.applyAndroidManifest = void 0;
|
|
4
|
+
var android_1 = require("./android");
|
|
5
|
+
Object.defineProperty(exports, "applyAndroidManifest", { enumerable: true, get: function () { return android_1.applyAndroidManifest; } });
|
|
6
|
+
var errors_1 = require("./errors");
|
|
7
|
+
Object.defineProperty(exports, "TransformError", { enumerable: true, get: function () { return errors_1.TransformError; } });
|
|
8
|
+
var ios_1 = require("./ios");
|
|
9
|
+
Object.defineProperty(exports, "applyInfoPlist", { enumerable: true, get: function () { return ios_1.applyInfoPlist; } });
|
|
10
|
+
Object.defineProperty(exports, "applyXcodeProject", { enumerable: true, get: function () { return ios_1.applyXcodeProject; } });
|
|
11
|
+
var types_1 = require("./types");
|
|
12
|
+
Object.defineProperty(exports, "renderTypes", { enumerable: true, get: function () { return types_1.renderTypes; } });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { XcodeProject } from 'xcode';
|
|
2
|
+
import type { ResolvedConfig } from '../types';
|
|
3
|
+
export declare function applyInfoPlist(plist: Record<string, unknown>, cfg: ResolvedConfig): Record<string, unknown>;
|
|
4
|
+
export declare function applyXcodeProject(project: XcodeProject, cfg: ResolvedConfig): XcodeProject;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyInfoPlist = applyInfoPlist;
|
|
4
|
+
exports.applyXcodeProject = applyXcodeProject;
|
|
5
|
+
const PRIMARY_ICON_NAME = 'AppIcon';
|
|
6
|
+
function applyInfoPlist(plist, cfg) {
|
|
7
|
+
const next = { ...plist };
|
|
8
|
+
next.IconSwitcherIcons = cfg.icons
|
|
9
|
+
.filter((icon) => icon.isDefault || icon.ios !== null)
|
|
10
|
+
.map((icon) => icon.name);
|
|
11
|
+
next.CFBundleIcons = _ensurePrimaryIconBundle(next.CFBundleIcons, 'CFBundleIcons');
|
|
12
|
+
if (_supportsIpad(next)) {
|
|
13
|
+
next['CFBundleIcons~ipad'] = _mirrorIpadIcons(next.CFBundleIcons, next['CFBundleIcons~ipad']);
|
|
14
|
+
}
|
|
15
|
+
return next;
|
|
16
|
+
}
|
|
17
|
+
function _ensurePrimaryIconBundle(existing, label) {
|
|
18
|
+
const bundle = existing && typeof existing === 'object' && !Array.isArray(existing)
|
|
19
|
+
? { ...existing }
|
|
20
|
+
: {};
|
|
21
|
+
const primary = bundle.CFBundlePrimaryIcon && typeof bundle.CFBundlePrimaryIcon === 'object'
|
|
22
|
+
? { ...bundle.CFBundlePrimaryIcon }
|
|
23
|
+
: {};
|
|
24
|
+
const currentName = primary.CFBundleIconName;
|
|
25
|
+
if (currentName !== undefined && currentName !== PRIMARY_ICON_NAME) {
|
|
26
|
+
console.warn(`[react-native-icon-switcher] ${label}.CFBundlePrimaryIcon.CFBundleIconName is "${String(currentName)}"; expected "${PRIMARY_ICON_NAME}" — leaving unchanged`);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
primary.CFBundleIconName = PRIMARY_ICON_NAME;
|
|
30
|
+
}
|
|
31
|
+
bundle.CFBundlePrimaryIcon = primary;
|
|
32
|
+
return bundle;
|
|
33
|
+
}
|
|
34
|
+
function _supportsIpad(plist) {
|
|
35
|
+
const families = plist.UIDeviceFamily;
|
|
36
|
+
if (Array.isArray(families) &&
|
|
37
|
+
families.some((value) => value === 2 || value === '2')) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return plist['UISupportedInterfaceOrientations~ipad'] !== undefined;
|
|
41
|
+
}
|
|
42
|
+
function _mirrorIpadIcons(phoneIcons, existingIpad) {
|
|
43
|
+
const phone = phoneIcons && typeof phoneIcons === 'object' && !Array.isArray(phoneIcons)
|
|
44
|
+
? phoneIcons
|
|
45
|
+
: {};
|
|
46
|
+
const ipad = existingIpad &&
|
|
47
|
+
typeof existingIpad === 'object' &&
|
|
48
|
+
!Array.isArray(existingIpad)
|
|
49
|
+
? { ...existingIpad }
|
|
50
|
+
: {};
|
|
51
|
+
ipad.CFBundlePrimaryIcon = phone.CFBundlePrimaryIcon;
|
|
52
|
+
return ipad;
|
|
53
|
+
}
|
|
54
|
+
function applyXcodeProject(project, cfg) {
|
|
55
|
+
const alternateNames = cfg.icons
|
|
56
|
+
.filter((icon) => !icon.isDefault && icon.ios !== null)
|
|
57
|
+
.map((icon) => `AppIcon-${icon.name}`)
|
|
58
|
+
.join(' ');
|
|
59
|
+
const [, nativeTarget] = _findFirstApplicationTarget(project);
|
|
60
|
+
const buildConfigurations = _getBuildConfigurationsForListId(project, nativeTarget.buildConfigurationList);
|
|
61
|
+
for (const [, configuration] of buildConfigurations) {
|
|
62
|
+
configuration.buildSettings.ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES =
|
|
63
|
+
_formatAlternateAppIconNamesSetting(alternateNames);
|
|
64
|
+
configuration.buildSettings.ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS =
|
|
65
|
+
'NO';
|
|
66
|
+
}
|
|
67
|
+
return project;
|
|
68
|
+
}
|
|
69
|
+
function _formatAlternateAppIconNamesSetting(raw) {
|
|
70
|
+
if (!raw) {
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
if (!raw.includes(' ')) {
|
|
74
|
+
return raw;
|
|
75
|
+
}
|
|
76
|
+
return `"${raw}"`;
|
|
77
|
+
}
|
|
78
|
+
function _findFirstApplicationTarget(project) {
|
|
79
|
+
var _a;
|
|
80
|
+
const section = project.pbxNativeTargetSection();
|
|
81
|
+
for (const [key, value] of Object.entries(section)) {
|
|
82
|
+
if (key.endsWith('_comment')) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const target = value;
|
|
86
|
+
const productType = _unquote(String((_a = target.productType) !== null && _a !== void 0 ? _a : ''));
|
|
87
|
+
if (productType === 'com.apple.product-type.application') {
|
|
88
|
+
return [key, target];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new Error('Could not find application target in Xcode project');
|
|
92
|
+
}
|
|
93
|
+
function _getBuildConfigurationsForListId(project, configurationListId) {
|
|
94
|
+
var _a;
|
|
95
|
+
const lists = project.pbxXCConfigurationList();
|
|
96
|
+
const list = lists[configurationListId];
|
|
97
|
+
const ids = ((_a = list === null || list === void 0 ? void 0 : list.buildConfigurations) !== null && _a !== void 0 ? _a : []).map((entry) => entry.value);
|
|
98
|
+
const section = project.pbxXCBuildConfigurationSection();
|
|
99
|
+
return Object.entries(section).filter(([key, value]) => {
|
|
100
|
+
if (key.endsWith('_comment')) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const item = value;
|
|
104
|
+
return item.isa === 'XCBuildConfiguration' && ids.includes(key);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function _unquote(value) {
|
|
108
|
+
var _a;
|
|
109
|
+
const match = value.match(/^"(.*)"$/);
|
|
110
|
+
return (_a = match === null || match === void 0 ? void 0 : match[1]) !== null && _a !== void 0 ? _a : value;
|
|
111
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.halloweenOnlyConfig = exports.sampleConfig = void 0;
|
|
4
|
+
exports.sampleConfig = {
|
|
5
|
+
icons: [
|
|
6
|
+
{
|
|
7
|
+
name: 'default',
|
|
8
|
+
isDefault: true,
|
|
9
|
+
ios: { light: 'icons/default.png' },
|
|
10
|
+
android: { legacy: 'icons/default.png' },
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'halloween',
|
|
14
|
+
isDefault: false,
|
|
15
|
+
ios: { light: 'icons/halloween.png' },
|
|
16
|
+
android: { legacy: 'icons/halloween.png' },
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
ios: { flattenOn: '#FFFFFF' },
|
|
20
|
+
android: { applyOn: 'background', skipBrands: ['samsung'] },
|
|
21
|
+
};
|
|
22
|
+
exports.halloweenOnlyConfig = {
|
|
23
|
+
icons: [
|
|
24
|
+
{
|
|
25
|
+
name: 'default',
|
|
26
|
+
isDefault: true,
|
|
27
|
+
ios: { light: 'icons/default.png' },
|
|
28
|
+
android: { legacy: 'icons/default.png' },
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
ios: { flattenOn: '#FFFFFF' },
|
|
32
|
+
android: { applyOn: 'immediate', skipBrands: [] },
|
|
33
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderTypes = renderTypes;
|
|
4
|
+
function renderTypes(cfg) {
|
|
5
|
+
const iconUnion = cfg.icons.map((icon) => `'${icon.name}'`).join(' | ');
|
|
6
|
+
return `import 'react-native-icon-switcher';
|
|
7
|
+
|
|
8
|
+
declare module 'react-native-icon-switcher' {
|
|
9
|
+
interface Register {
|
|
10
|
+
icons: ${iconUnion};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirror of `src/types.ts` (P2). Keep identical so plugin build-time code matches the package contract.
|
|
3
|
+
*/
|
|
4
|
+
/** A path to a square PNG master, ideally 1024 px. Relative to the project root. */
|
|
5
|
+
export type ImagePath = string;
|
|
6
|
+
/** Hex colour, `#RGB` or `#RRGGBB`. */
|
|
7
|
+
export type HexColor = string;
|
|
8
|
+
export interface IosIconVariants {
|
|
9
|
+
light: ImagePath;
|
|
10
|
+
/** Phase 2. */
|
|
11
|
+
dark?: ImagePath;
|
|
12
|
+
/** Phase 2. */
|
|
13
|
+
tinted?: ImagePath;
|
|
14
|
+
}
|
|
15
|
+
export interface AndroidAdaptiveIcon {
|
|
16
|
+
foreground: ImagePath;
|
|
17
|
+
background: ImagePath | HexColor;
|
|
18
|
+
/** Phase 2. Android 13+ themed icons. */
|
|
19
|
+
monochrome?: ImagePath;
|
|
20
|
+
}
|
|
21
|
+
export interface IconSpec {
|
|
22
|
+
ios?: ImagePath | IosIconVariants;
|
|
23
|
+
android?: ImagePath | AndroidAdaptiveIcon;
|
|
24
|
+
}
|
|
25
|
+
/** A bare path means "same master for both platforms". */
|
|
26
|
+
export type IconSource = ImagePath | IconSpec;
|
|
27
|
+
export interface IconSwitcherConfig {
|
|
28
|
+
/** Primary icon. Omit on Expo to reuse `icon` / `android.adaptiveIcon`. */
|
|
29
|
+
default?: IconSource;
|
|
30
|
+
icons: Record<string, IconSource>;
|
|
31
|
+
ios?: {
|
|
32
|
+
/** Background colour that transparent pixels are flattened onto. Default `#FFFFFF`. */
|
|
33
|
+
flattenOn?: HexColor;
|
|
34
|
+
};
|
|
35
|
+
android?: {
|
|
36
|
+
/** Default `'background'`. */
|
|
37
|
+
applyOn?: 'background' | 'immediate';
|
|
38
|
+
/** Lower-cased `Build.BRAND` values where switching is disabled. Default `[]`. */
|
|
39
|
+
skipBrands?: string[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Config after validation and defaults; every icon normalised to per-platform specs. */
|
|
43
|
+
export interface ResolvedIcon {
|
|
44
|
+
name: string;
|
|
45
|
+
isDefault: boolean;
|
|
46
|
+
ios: IosIconVariants | null;
|
|
47
|
+
android: AndroidAdaptiveIcon | {
|
|
48
|
+
legacy: ImagePath;
|
|
49
|
+
} | null;
|
|
50
|
+
}
|
|
51
|
+
export interface ResolvedConfig {
|
|
52
|
+
/** `default` first, then alternates in declaration order. */
|
|
53
|
+
icons: ResolvedIcon[];
|
|
54
|
+
ios: {
|
|
55
|
+
flattenOn: HexColor;
|
|
56
|
+
};
|
|
57
|
+
android: {
|
|
58
|
+
applyOn: 'background' | 'immediate';
|
|
59
|
+
skipBrands: string[];
|
|
60
|
+
};
|
|
61
|
+
}
|