react-native-icon-switcher 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Switch the app icon at runtime on React Native and Expo from one config file, wi
|
|
|
22
22
|
npx expo install react-native-icon-switcher
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Add the config plugin in `app.json` / `app.config.ts`
|
|
25
|
+
Add the config plugin in `app.json` / `app.config.ts` and pass the icon config as its options (same shape as [Config](#config)):
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
28
|
export default {
|
|
@@ -44,6 +44,20 @@ export default {
|
|
|
44
44
|
|
|
45
45
|
Then run `npx expo prebuild` or use a development build. Expo Go is not supported.
|
|
46
46
|
|
|
47
|
+
The plugin only reads its inline options; it does not look for `icon-switcher.config.ts` on its own. To keep the config in its own file (for example to share it with the `sync` CLI), import it in `app.config.ts` with the explicit extension:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import iconSwitcherConfig from './icon-switcher.config.ts';
|
|
51
|
+
|
|
52
|
+
export default {
|
|
53
|
+
expo: {
|
|
54
|
+
plugins: [['react-native-icon-switcher', iconSwitcherConfig]],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Expo transpiles `app.config.ts` but not the files it imports, so this relies on Node's built-in TypeScript type stripping (Node 22.18 or later). On older Node versions, write the config as `icon-switcher.config.js` instead.
|
|
60
|
+
|
|
47
61
|
### Bare React Native
|
|
48
62
|
|
|
49
63
|
```sh
|
|
@@ -61,13 +75,27 @@ npx react-native-icon-switcher sync
|
|
|
61
75
|
|
|
62
76
|
Use `npx react-native-icon-switcher sync --check` in CI to fail when generated native files or `icon-switcher.d.ts` are out of date.
|
|
63
77
|
|
|
78
|
+
```sh
|
|
79
|
+
react-native-icon-switcher sync [--check] [--project-root <dir>] [--config <path>]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Flag | Default | Description |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `--check` | off | Write nothing; print each out-of-date path and exit `1` if any. |
|
|
85
|
+
| `--project-root <dir>` | current directory | App root containing `ios/` and `android/` (useful in monorepos). |
|
|
86
|
+
| `--config <path>` | first of `icon-switcher.config.{ts,js,mjs,cjs}` in the project root | Config file to load. TypeScript is loaded directly, with no compile step. |
|
|
87
|
+
|
|
88
|
+
Exit codes: `0` success (or no drift with `--check`), `1` drift found by `--check`, `2` invalid arguments, config errors, or an unexpected project layout. Without `--check` it prints `updated N files, unchanged M`.
|
|
89
|
+
|
|
64
90
|
**No `expo` package is required** for bare apps: this library is a TurboModule with a standalone `sync` CLI. You only need `expo` if you opt into the Expo config plugin workflow.
|
|
65
91
|
|
|
66
92
|
See `example/` (Expo) and `example-bare/` (bare CLI + `sync`) in this repo.
|
|
67
93
|
|
|
68
94
|
## Config
|
|
69
95
|
|
|
70
|
-
Bare apps use `icon-switcher.config.ts` at the project root. Expo apps
|
|
96
|
+
Bare apps use `icon-switcher.config.ts` at the project root. Expo apps pass the same shape as plugin options (or import that file, see [Expo](#expo)).
|
|
97
|
+
|
|
98
|
+
`defineIcons` has no runtime effect: it returns its argument unchanged and exists so TypeScript checks the object as you write it (misspelled keys, wrong `applyOn` values, a missing adaptive `background`) and offers autocomplete. Value rules are checked when `sync` or `expo prebuild` runs, and every problem is reported at once.
|
|
71
99
|
|
|
72
100
|
```ts
|
|
73
101
|
import { defineIcons } from 'react-native-icon-switcher/config';
|
|
@@ -105,12 +133,31 @@ Per-icon `ios` / `android` fields accept a path or structured variants (`IosIcon
|
|
|
105
133
|
|
|
106
134
|
Icons with no art for a platform are omitted from that platform's generated outputs.
|
|
107
135
|
|
|
136
|
+
**Validation:** `sync` and the Expo plugin fail if `icons` is empty, a name does not match `/^[a-z][a-z0-9_]*$/` or uses `default`, a colour is not `#RGB` / `#RRGGBB`, an image file does not exist, or Expo's default icon and `default` point at different files.
|
|
137
|
+
|
|
108
138
|
**Android reserved names:** names may not be `round`, `foreground`, `background`, or `monochrome`, nor end in `_round`, `_foreground`, `_background`, or `_monochrome`.
|
|
109
139
|
|
|
140
|
+
**Android adaptive icons:** layers are 108×108 dp, but launchers show only the central 72×72 dp and then mask it (circle, squircle, …). Keep logos and text inside that centre, about the middle two thirds of the image. To fit artwork that fills its square, scale it into the centre of the `background` image on a matching colour and use a transparent `foreground`.
|
|
141
|
+
|
|
110
142
|
**Legacy Android icons (API 24–25):** besides the adaptive layers and `mipmap-anydpi-v26` XML, `sync` and the plugin write `ic_launcher*.png` the way Android Studio's Image Asset tool does: the visible centre of an adaptive icon (72 dp of the 108 dp layers; a plain path uses the whole image) in a 38 dp rounded square or a 44 dp circle, with a soft drop shadow, on a transparent 48 dp canvas.
|
|
111
143
|
|
|
144
|
+
**Variants (experimental):** iOS `dark` / `tinted` and Android `monochrome` (themed icons, Android 13+) are generated when set, but have not yet been verified on devices.
|
|
145
|
+
|
|
146
|
+
### What `sync` and the plugin generate
|
|
147
|
+
|
|
148
|
+
| | iOS | Android |
|
|
149
|
+
| --- | --- | --- |
|
|
150
|
+
| Images | `AppIcon-<name>.appiconset` in `Images.xcassets` (1024 px, flattened onto `ios.flattenOn`) | `mipmap-*/ic_launcher_<name>*` in five densities, plus `mipmap-anydpi-v26` adaptive XML |
|
|
151
|
+
| Registration | `ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES` and `ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS` build settings | One `activity-alias` per icon, `.IconSwitcher_<name>`; `MainActivity` loses its launcher filter |
|
|
152
|
+
| Runtime list | `IconSwitcherIcons` array in `Info.plist` | `com.iconswitcher.*` meta-data in `AndroidManifest.xml` |
|
|
153
|
+
| Types | `icon-switcher.d.ts` at the project root | same file |
|
|
154
|
+
|
|
155
|
+
Xcode generates `CFBundleIcons` from the build settings, so the library does not write it. Other content in these files is left as it is: comments, formatting, and the quoting CocoaPods applies to the pbxproj, so `sync --check` stays clean after `pod install`. In bare apps, commit the generated files. With Expo prebuild, commit `icon-switcher.d.ts`; native folders follow your usual prebuild policy.
|
|
156
|
+
|
|
112
157
|
**Managed iOS icon sets:** when `sync` or the plugin rewrites an `.appiconset` it owns, it deletes files in that folder that the new `Contents.json` does not reference (`sync --check` reports them). Other icon sets are left alone.
|
|
113
158
|
|
|
159
|
+
**Generated images across machines:** image resampling rounds slightly differently on different CPUs (for example an Apple silicon Mac and x86 Linux CI). `sync` treats an existing PNG whose pixels differ from the regenerated one by at most 3 levels per channel as unchanged, so `sync --check` stays green and files are not rewritten.
|
|
160
|
+
|
|
114
161
|
## Usage
|
|
115
162
|
|
|
116
163
|
```ts
|
|
@@ -133,7 +180,7 @@ if (IconSwitcher.isSupported) {
|
|
|
133
180
|
|
|
134
181
|
| Method | Returns | Notes |
|
|
135
182
|
| --- | --- | --- |
|
|
136
|
-
| `IconSwitcher.isSupported` | `boolean` | `false` when the TurboModule is missing (web, Expo Go, unlink)
|
|
183
|
+
| `IconSwitcher.isSupported` | `boolean` | `false` when the TurboModule is missing (web, Expo Go, unlink), the device brand is skipped, or iOS reports `supportsAlternateIcons` as `false`. |
|
|
137
184
|
| `IconSwitcher.list()` | `IconName[]` | Bundled icon names; `default` first. |
|
|
138
185
|
| `IconSwitcher.getCurrent()` | `Promise<IconName>` | |
|
|
139
186
|
| `IconSwitcher.set(name)` | `Promise<{ status: 'applied' \| 'pending' }>` | Android with `applyOn: 'background'` → `pending` until background or `applyPending()`. |
|
|
@@ -181,7 +228,7 @@ const unsubscribe = await syncIcon({
|
|
|
181
228
|
const res = await fetch('https://api.example.com/icon');
|
|
182
229
|
return (await res.json()).iconName as string;
|
|
183
230
|
},
|
|
184
|
-
storage:
|
|
231
|
+
storage: AsyncStorage, // optional; default: in-memory
|
|
185
232
|
retryAfterMs: 24 * 60 * 60 * 1000, // skip re-apply after failure
|
|
186
233
|
onError: (err) => console.warn(err),
|
|
187
234
|
});
|
|
@@ -192,6 +239,10 @@ const unsubscribe = await syncIcon({
|
|
|
192
239
|
|
|
193
240
|
On **iOS**, the helper runs when the app is `active` and subscribes to `AppState`. On **Android**, it runs once when called (`set` may still return `pending`).
|
|
194
241
|
|
|
242
|
+
Each run does nothing when `fetchChoice` returns `null`, `undefined`, `''`, or a name not in `list()`, or when the choice is already current. A `pending` result is not recorded as applied.
|
|
243
|
+
|
|
244
|
+
`storage` is any object with async `getItem(key): Promise<string | null>` and `setItem(key, value): Promise<void>` (`SyncIconStorage`), such as `@react-native-async-storage/async-storage`. The helper uses the keys `icon-switcher:lastApplied` and `icon-switcher:lastFailed`. With the default in-memory storage, the retry block lasts only as long as that `syncIcon` call, so a restart retries straight away.
|
|
245
|
+
|
|
195
246
|
## Testing with Jest
|
|
196
247
|
|
|
197
248
|
Ship a CommonJS mock that never loads native code:
|
|
@@ -247,6 +298,7 @@ Summary: with default `applyOn: 'background'`, `set()` returns `pending` and the
|
|
|
247
298
|
## Requirements
|
|
248
299
|
|
|
249
300
|
- **New Architecture (TurboModule) required** — React Native **≥ 0.82** (developed with **0.88**).
|
|
301
|
+
- **iOS 15.1+** (React Native's `min_ios_version_supported`) and **Android minSdk 24**. On Android the module adds `androidx.lifecycle:lifecycle-process` to detect when the app goes to the background.
|
|
250
302
|
- **Expo SDK ≥ 52** when using the config plugin (optional peer; developed with **SDK 58**).
|
|
251
303
|
- **Expo Go is not supported** — use a development build or bare native project.
|
|
252
304
|
|
|
@@ -48,6 +48,16 @@ function _isFailedBlocked(failed, choice, retryAfterMs) {
|
|
|
48
48
|
return Date.now() - failed.at < retryAfterMs;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Shared by every syncIcon instance: callers restart syncIcon when their inputs change, and an
|
|
52
|
+
// older instance's run can still be waiting on the OS (iOS shows an alert per set call). Running
|
|
53
|
+
// one at a time lets the next run see the icon the previous one applied.
|
|
54
|
+
let _queue = Promise.resolve();
|
|
55
|
+
function _enqueue(task) {
|
|
56
|
+
const next = _queue.then(task, task);
|
|
57
|
+
_queue = next.catch(() => {});
|
|
58
|
+
return next;
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
/**
|
|
52
62
|
* Fetches the remote icon choice, validates it, and applies it via {@link IconSwitcher}.
|
|
53
63
|
* iOS: runs while the app is `active`. Android: calls `set` immediately (native may defer).
|
|
@@ -64,13 +74,17 @@ export async function syncIcon(options) {
|
|
|
64
74
|
}
|
|
65
75
|
};
|
|
66
76
|
let inFlight = false;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
inFlight = true;
|
|
77
|
+
let stopped = false;
|
|
78
|
+
const _apply = async () => {
|
|
72
79
|
try {
|
|
80
|
+
// Unsubscribed while queued: the caller has moved on to a newer syncIcon.
|
|
81
|
+
if (stopped) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
73
84
|
const choice = await options.fetchChoice();
|
|
85
|
+
if (stopped) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
74
88
|
if (choice == null || choice === '') {
|
|
75
89
|
return;
|
|
76
90
|
}
|
|
@@ -103,6 +117,15 @@ export async function syncIcon(options) {
|
|
|
103
117
|
}
|
|
104
118
|
} catch (error) {
|
|
105
119
|
reportError(error);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const run = async () => {
|
|
123
|
+
if (inFlight || stopped) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
inFlight = true;
|
|
127
|
+
try {
|
|
128
|
+
await _enqueue(_apply);
|
|
106
129
|
} finally {
|
|
107
130
|
inFlight = false;
|
|
108
131
|
}
|
|
@@ -119,9 +142,14 @@ export async function syncIcon(options) {
|
|
|
119
142
|
}
|
|
120
143
|
});
|
|
121
144
|
runWhenActive();
|
|
122
|
-
return () =>
|
|
145
|
+
return () => {
|
|
146
|
+
stopped = true;
|
|
147
|
+
subscription.remove();
|
|
148
|
+
};
|
|
123
149
|
}
|
|
124
150
|
await run();
|
|
125
|
-
return () => {
|
|
151
|
+
return () => {
|
|
152
|
+
stopped = true;
|
|
153
|
+
};
|
|
126
154
|
}
|
|
127
155
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["AppState","Platform","IconSwitcher","STORAGE_KEY_APPLIED","STORAGE_KEY_FAILED","DEFAULT_RETRY_AFTER_MS","_createMemoryStorage","data","Map","getItem","key","get","setItem","value","set","_encodeFailed","name","JSON","stringify","at","Date","now","_decodeFailed","raw","length","parsed","parse","_isFailedBlocked","failed","choice","retryAfterMs","syncIcon","options","storage","reportError","error","onError","inFlight","
|
|
1
|
+
{"version":3,"names":["AppState","Platform","IconSwitcher","STORAGE_KEY_APPLIED","STORAGE_KEY_FAILED","DEFAULT_RETRY_AFTER_MS","_createMemoryStorage","data","Map","getItem","key","get","setItem","value","set","_encodeFailed","name","JSON","stringify","at","Date","now","_decodeFailed","raw","length","parsed","parse","_isFailedBlocked","failed","choice","retryAfterMs","_queue","Promise","resolve","_enqueue","task","next","then","catch","syncIcon","options","storage","reportError","error","onError","inFlight","stopped","_apply","fetchChoice","list","includes","isSupported","failedRaw","current","getCurrent","result","status","run","runWhenActive","currentState","OS","subscription","addEventListener","state","remove"],"sourceRoot":"../../../src","sources":["remote/index.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/remote/index.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtD,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/remote/index.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtD,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAwDD;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAkG5E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-icon-switcher",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Switch the app icon at runtime on React Native and Expo from one config file, with the native wiring generated for you",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/remote/index.ts
CHANGED
|
@@ -64,6 +64,17 @@ function _isFailedBlocked(
|
|
|
64
64
|
return Date.now() - failed.at < retryAfterMs;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Shared by every syncIcon instance: callers restart syncIcon when their inputs change, and an
|
|
68
|
+
// older instance's run can still be waiting on the OS (iOS shows an alert per set call). Running
|
|
69
|
+
// one at a time lets the next run see the icon the previous one applied.
|
|
70
|
+
let _queue: Promise<void> = Promise.resolve();
|
|
71
|
+
|
|
72
|
+
function _enqueue(task: () => Promise<void>): Promise<void> {
|
|
73
|
+
const next = _queue.then(task, task);
|
|
74
|
+
_queue = next.catch(() => {});
|
|
75
|
+
return next;
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
/**
|
|
68
79
|
* Fetches the remote icon choice, validates it, and applies it via {@link IconSwitcher}.
|
|
69
80
|
* iOS: runs while the app is `active`. Android: calls `set` immediately (native may defer).
|
|
@@ -81,14 +92,18 @@ export async function syncIcon(options: SyncIconOptions): Promise<() => void> {
|
|
|
81
92
|
};
|
|
82
93
|
|
|
83
94
|
let inFlight = false;
|
|
95
|
+
let stopped = false;
|
|
84
96
|
|
|
85
|
-
const
|
|
86
|
-
if (inFlight) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
inFlight = true;
|
|
97
|
+
const _apply = async (): Promise<void> => {
|
|
90
98
|
try {
|
|
99
|
+
// Unsubscribed while queued: the caller has moved on to a newer syncIcon.
|
|
100
|
+
if (stopped) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
91
103
|
const choice = await options.fetchChoice();
|
|
104
|
+
if (stopped) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
92
107
|
if (choice == null || choice === '') {
|
|
93
108
|
return;
|
|
94
109
|
}
|
|
@@ -124,6 +139,16 @@ export async function syncIcon(options: SyncIconOptions): Promise<() => void> {
|
|
|
124
139
|
}
|
|
125
140
|
} catch (error) {
|
|
126
141
|
reportError(error);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const run = async (): Promise<void> => {
|
|
146
|
+
if (inFlight || stopped) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
inFlight = true;
|
|
150
|
+
try {
|
|
151
|
+
await _enqueue(_apply);
|
|
127
152
|
} finally {
|
|
128
153
|
inFlight = false;
|
|
129
154
|
}
|
|
@@ -142,9 +167,14 @@ export async function syncIcon(options: SyncIconOptions): Promise<() => void> {
|
|
|
142
167
|
}
|
|
143
168
|
});
|
|
144
169
|
runWhenActive();
|
|
145
|
-
return () =>
|
|
170
|
+
return () => {
|
|
171
|
+
stopped = true;
|
|
172
|
+
subscription.remove();
|
|
173
|
+
};
|
|
146
174
|
}
|
|
147
175
|
|
|
148
176
|
await run();
|
|
149
|
-
return () => {
|
|
177
|
+
return () => {
|
|
178
|
+
stopped = true;
|
|
179
|
+
};
|
|
150
180
|
}
|