expo-app-icon 1.1.0 → 1.3.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </div>
4
4
 
5
5
  # Expo App Icon
6
- Programmatically change your app's icon at runtime in Expo, with proper Android adaptive-icon support.
6
+ Programmatically change your app's icon at runtime in Expo.
7
7
 
8
8
  ## Install
9
9
 
@@ -31,19 +31,26 @@ Add the plugin to your `app.json` config and declare your icons. Each icon is ju
31
31
  }
32
32
  ```
33
33
 
34
- The same image is used for both platforms. To use a different image per platform (or mark an iOS icon as prerendered), pass an object instead:
34
+ The same image is used for both platforms. For per-platform images, or to attach
35
+ metadata (any key/value you want — labels, descriptions, premium flags, …), use the object form:
35
36
 
36
37
  ```json
37
38
  {
38
39
  "red": "./assets/icons/red.png",
39
40
  "blue": {
40
- "ios": "./assets/icons/blue-ios.png",
41
- "android": "./assets/icons/blue-android.png",
42
- "prerendered": true
41
+ "image": "./assets/icons/blue.png",
42
+ "metadata": { "label": "Blue", "description": "Cool blue", "isPremium": true }
43
+ },
44
+ "split": {
45
+ "ios": "./assets/icons/split-ios.png",
46
+ "android": "./assets/icons/split-android.png"
43
47
  }
44
48
  }
45
49
  ```
46
50
 
51
+ - `image` sets both platforms; `ios` / `android` override it per platform.
52
+ - `metadata` is passed straight through to `getAvailableIcons()` at runtime (it's ignored when generating the icons).
53
+
47
54
  Then create a new build (the plugin runs during prebuild):
48
55
 
49
56
  ```sh
@@ -52,15 +59,35 @@ npx expo prebuild --clean
52
59
 
53
60
  ## Usage
54
61
 
62
+ Build a picker with the `useAppIcon` hook and `getAvailableIcons` — no per-app boilerplate or persistence needed (the native module is the source of truth):
63
+
64
+ ```tsx
65
+ import { useAppIcon, getAvailableIcons } from "expo-app-icon";
66
+
67
+ type Meta = { label: string; description?: string; isPremium?: boolean };
68
+
69
+ function IconPicker() {
70
+ const { icon, setIcon, isDefault } = useAppIcon();
71
+
72
+ return getAvailableIcons<Meta>().map(({ name, metadata }) => (
73
+ <Pressable key={name} onPress={() => setIcon(name)}>
74
+ <Text>{metadata.label}{icon === name ? " ✓" : ""}</Text>
75
+ </Pressable>
76
+ ));
77
+ }
78
+ ```
79
+
80
+ - `icon` — the current icon key, or `null` for the default.
81
+ - `setIcon(name | null)` — switch icons; pass `null` to reset to the default. (The iOS timing fix is built in.)
82
+ - `isDefault` / `isSupported` — handy flags (`isSupported` is `false` on web).
83
+ - `getAvailableIcons<Meta>()` — the configured icons + their metadata, typed to `Meta`.
84
+
85
+ Or call the underlying functions directly:
86
+
55
87
  ```ts
56
88
  import { getAppIcon, setAppIcon } from "expo-app-icon";
57
89
 
58
- // Get the name of the current icon ("DEFAULT" when none is set)
59
- const current = getAppIcon();
60
-
61
- // Switch to one of your configured icons by key
90
+ const current = getAppIcon(); // icon name, or "DEFAULT"
62
91
  setAppIcon("red");
63
-
64
- // Reset back to the default icon
65
- setAppIcon(null);
92
+ setAppIcon(null); // reset to default
66
93
  ```
@@ -0,0 +1,19 @@
1
+ import type { DynamicAppIconRegistry } from "./types";
2
+ type IconName = DynamicAppIconRegistry["IconName"];
3
+ /**
4
+ * One configured icon and the metadata declared for it in the plugin config.
5
+ */
6
+ export type AppIconEntry<M = Record<string, unknown>> = {
7
+ /** The configured icon key. */
8
+ name: IconName;
9
+ /** Arbitrary metadata attached in app.json (label, description, isPremium, …). */
10
+ metadata: M;
11
+ };
12
+ /**
13
+ * The icons configured in app.json, with their metadata. Generic over the
14
+ * consumer's metadata shape, e.g.
15
+ * `getAvailableIcons<{ label: string; isPremium?: boolean }>()`.
16
+ */
17
+ export declare function getAvailableIcons<M = Record<string, unknown>>(): AppIconEntry<M>[];
18
+ export {};
19
+ //# sourceMappingURL=icons-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons-data.d.ts","sourceRoot":"","sources":["../src/icons-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAGtD,KAAK,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACtD,+BAA+B;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,kFAAkF;IAClF,QAAQ,EAAE,CAAC,CAAC;CACb,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACxB,YAAY,CAAC,CAAC,CAAC,EAAE,CAErB"}
@@ -0,0 +1,7 @@
1
+ import type { AppIconEntry } from "./icons-data";
2
+ /**
3
+ * Placeholder. The expo-app-icon config plugin overwrites this file's compiled
4
+ * output during prebuild with the icons + metadata declared in app.json.
5
+ */
6
+ export declare const ICON_DATA: AppIconEntry[];
7
+ //# sourceMappingURL=icons-data.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons-data.generated.d.ts","sourceRoot":"","sources":["../src/icons-data.generated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,YAAY,EAAO,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Placeholder. The expo-app-icon config plugin overwrites this file's compiled
3
+ * output during prebuild with the icons + metadata declared in app.json.
4
+ */
5
+ export const ICON_DATA = [];
6
+ //# sourceMappingURL=icons-data.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons-data.generated.js","sourceRoot":"","sources":["../src/icons-data.generated.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAmB,EAAE,CAAC","sourcesContent":["import type { AppIconEntry } from \"./icons-data\";\n\n/**\n * Placeholder. The expo-app-icon config plugin overwrites this file's compiled\n * output during prebuild with the icons + metadata declared in app.json.\n */\nexport const ICON_DATA: AppIconEntry[] = [];\n"]}
@@ -0,0 +1,10 @@
1
+ import { ICON_DATA } from "./icons-data.generated";
2
+ /**
3
+ * The icons configured in app.json, with their metadata. Generic over the
4
+ * consumer's metadata shape, e.g.
5
+ * `getAvailableIcons<{ label: string; isPremium?: boolean }>()`.
6
+ */
7
+ export function getAvailableIcons() {
8
+ return ICON_DATA;
9
+ }
10
+ //# sourceMappingURL=icons-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons-data.js","sourceRoot":"","sources":["../src/icons-data.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAcnD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAG/B,OAAO,SAAyC,CAAC;AACnD,CAAC","sourcesContent":["import type { DynamicAppIconRegistry } from \"./types\";\nimport { ICON_DATA } from \"./icons-data.generated\";\n\ntype IconName = DynamicAppIconRegistry[\"IconName\"];\n\n/**\n * One configured icon and the metadata declared for it in the plugin config.\n */\nexport type AppIconEntry<M = Record<string, unknown>> = {\n /** The configured icon key. */\n name: IconName;\n /** Arbitrary metadata attached in app.json (label, description, isPremium, …). */\n metadata: M;\n};\n\n/**\n * The icons configured in app.json, with their metadata. Generic over the\n * consumer's metadata shape, e.g.\n * `getAvailableIcons<{ label: string; isPremium?: boolean }>()`.\n */\nexport function getAvailableIcons<\n M = Record<string, unknown>\n>(): AppIconEntry<M>[] {\n return ICON_DATA as unknown as AppIconEntry<M>[];\n}\n"]}
package/build/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import type { DynamicAppIconRegistry } from "./types";
2
+ export { useAppIcon } from "./useAppIcon";
3
+ export type { UseAppIcon } from "./useAppIcon";
4
+ export { getAvailableIcons } from "./icons-data";
5
+ export type { AppIconEntry } from "./icons-data";
2
6
  /**
3
7
  * Union of the icon keys declared in the plugin config (widened to string when none).
4
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAGtD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,YAAY,EAAG,SAAkB,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,QAAQ,GAAG,IAAI,GACpB,QAAQ,GAAG,OAAO,YAAY,GAAG,KAAK,CAExC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,QAAQ,GAAG,OAAO,YAAY,CAE3D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAGtD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,YAAY,EAAG,SAAkB,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,QAAQ,GAAG,IAAI,GACpB,QAAQ,GAAG,OAAO,YAAY,GAAG,KAAK,CAExC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,QAAQ,GAAG,OAAO,YAAY,CAE3D"}
package/build/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import NativeAppIcon from "./ExpoAppIconChangerModule";
2
+ export { useAppIcon } from "./useAppIcon";
3
+ export { getAvailableIcons } from "./icons-data";
2
4
  /**
3
5
  * Value reported / accepted for the project's default launcher icon.
4
6
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,aAAa,MAAM,4BAA4B,CAAC;AAOvD;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,SAAkB,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,IAAqB;IAErB,OAAO,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,aAAa,CAAC,UAAU,EAAE,CAAC;AACpC,CAAC","sourcesContent":["import type { DynamicAppIconRegistry } from \"./types\";\nimport NativeAppIcon from \"./ExpoAppIconChangerModule\";\n\n/**\n * Union of the icon keys declared in the plugin config (widened to string when none).\n */\nexport type IconName = DynamicAppIconRegistry[\"IconName\"];\n\n/**\n * Value reported / accepted for the project's default launcher icon.\n */\nexport const DEFAULT_ICON = \"DEFAULT\" as const;\n\n/**\n * Switch the launcher icon at runtime.\n *\n * @param name A configured icon key, or `null` to reset to the default icon.\n * @returns The applied icon name, `\"DEFAULT\"`, or `false` if unsupported.\n */\nexport function setAppIcon(\n name: IconName | null\n): IconName | typeof DEFAULT_ICON | false {\n return NativeAppIcon.setAppIcon(name);\n}\n\n/**\n * Get the currently active icon name, or `\"DEFAULT\"` when none is set.\n */\nexport function getAppIcon(): IconName | typeof DEFAULT_ICON {\n return NativeAppIcon.getAppIcon();\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,aAAa,MAAM,4BAA4B,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAQjD;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,SAAkB,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,IAAqB;IAErB,OAAO,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,aAAa,CAAC,UAAU,EAAE,CAAC;AACpC,CAAC","sourcesContent":["import type { DynamicAppIconRegistry } from \"./types\";\nimport NativeAppIcon from \"./ExpoAppIconChangerModule\";\n\nexport { useAppIcon } from \"./useAppIcon\";\nexport type { UseAppIcon } from \"./useAppIcon\";\nexport { getAvailableIcons } from \"./icons-data\";\nexport type { AppIconEntry } from \"./icons-data\";\n\n/**\n * Union of the icon keys declared in the plugin config (widened to string when none).\n */\nexport type IconName = DynamicAppIconRegistry[\"IconName\"];\n\n/**\n * Value reported / accepted for the project's default launcher icon.\n */\nexport const DEFAULT_ICON = \"DEFAULT\" as const;\n\n/**\n * Switch the launcher icon at runtime.\n *\n * @param name A configured icon key, or `null` to reset to the default icon.\n * @returns The applied icon name, `\"DEFAULT\"`, or `false` if unsupported.\n */\nexport function setAppIcon(\n name: IconName | null\n): IconName | typeof DEFAULT_ICON | false {\n return NativeAppIcon.setAppIcon(name);\n}\n\n/**\n * Get the currently active icon name, or `\"DEFAULT\"` when none is set.\n */\nexport function getAppIcon(): IconName | typeof DEFAULT_ICON {\n return NativeAppIcon.getAppIcon();\n}\n"]}
@@ -1,6 +1,14 @@
1
1
  import type { DynamicAppIconRegistry } from "./types";
2
+ import type { UseAppIcon } from "./useAppIcon";
3
+ export { getAvailableIcons } from "./icons-data";
4
+ export type { AppIconEntry } from "./icons-data";
5
+ export type { UseAppIcon } from "./useAppIcon";
2
6
  export type IconName = DynamicAppIconRegistry["IconName"];
3
7
  export declare const DEFAULT_ICON: "DEFAULT";
8
+ /**
9
+ * Web has no launcher icon; the hook reports an unsupported, default state.
10
+ */
11
+ export declare function useAppIcon(): UseAppIcon;
4
12
  /**
5
13
  * Web has no launcher icon; this is a no-op that reports failure.
6
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../src/index.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,MAAM,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAE1D,eAAO,MAAM,YAAY,EAAG,SAAkB,CAAC;AAE/C;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,QAAQ,GAAG,IAAI,GACrB,QAAQ,GAAG,OAAO,YAAY,GAAG,KAAK,CAGxC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,QAAQ,GAAG,OAAO,YAAY,CAG3D"}
1
+ {"version":3,"file":"index.web.d.ts","sourceRoot":"","sources":["../src/index.web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,MAAM,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAE1D,eAAO,MAAM,YAAY,EAAG,SAAkB,CAAC;AAE/C;;GAEG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAEvC;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,QAAQ,GAAG,IAAI,GACrB,QAAQ,GAAG,OAAO,YAAY,GAAG,KAAK,CAGxC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,QAAQ,GAAG,OAAO,YAAY,CAG3D"}
@@ -1,4 +1,11 @@
1
+ export { getAvailableIcons } from "./icons-data";
1
2
  export const DEFAULT_ICON = "DEFAULT";
3
+ /**
4
+ * Web has no launcher icon; the hook reports an unsupported, default state.
5
+ */
6
+ export function useAppIcon() {
7
+ return { icon: null, setIcon: () => { }, isDefault: true, isSupported: false };
8
+ }
2
9
  /**
3
10
  * Web has no launcher icon; this is a no-op that reports failure.
4
11
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.web.js","sourceRoot":"","sources":["../src/index.web.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,YAAY,GAAG,SAAkB,CAAC;AAE/C;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,KAAsB;IAEtB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACpD,OAAO,YAAY,CAAC;AACtB,CAAC","sourcesContent":["import type { DynamicAppIconRegistry } from \"./types\";\n\nexport type IconName = DynamicAppIconRegistry[\"IconName\"];\n\nexport const DEFAULT_ICON = \"DEFAULT\" as const;\n\n/**\n * Web has no launcher icon; this is a no-op that reports failure.\n */\nexport function setAppIcon(\n _name: IconName | null\n): IconName | typeof DEFAULT_ICON | false {\n console.error(\"setAppIcon is not supported on web\");\n return false;\n}\n\n/**\n * Web has no launcher icon; always reports the default.\n */\nexport function getAppIcon(): IconName | typeof DEFAULT_ICON {\n console.error(\"getAppIcon is not supported on web\");\n return DEFAULT_ICON;\n}\n"]}
1
+ {"version":3,"file":"index.web.js","sourceRoot":"","sources":["../src/index.web.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAMjD,MAAM,CAAC,MAAM,YAAY,GAAG,SAAkB,CAAC;AAE/C;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAChF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,KAAsB;IAEtB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACpD,OAAO,YAAY,CAAC;AACtB,CAAC","sourcesContent":["import type { DynamicAppIconRegistry } from \"./types\";\nimport type { UseAppIcon } from \"./useAppIcon\";\n\nexport { getAvailableIcons } from \"./icons-data\";\nexport type { AppIconEntry } from \"./icons-data\";\nexport type { UseAppIcon } from \"./useAppIcon\";\n\nexport type IconName = DynamicAppIconRegistry[\"IconName\"];\n\nexport const DEFAULT_ICON = \"DEFAULT\" as const;\n\n/**\n * Web has no launcher icon; the hook reports an unsupported, default state.\n */\nexport function useAppIcon(): UseAppIcon {\n return { icon: null, setIcon: () => {}, isDefault: true, isSupported: false };\n}\n\n/**\n * Web has no launcher icon; this is a no-op that reports failure.\n */\nexport function setAppIcon(\n _name: IconName | null\n): IconName | typeof DEFAULT_ICON | false {\n console.error(\"setAppIcon is not supported on web\");\n return false;\n}\n\n/**\n * Web has no launcher icon; always reports the default.\n */\nexport function getAppIcon(): IconName | typeof DEFAULT_ICON {\n console.error(\"getAppIcon is not supported on web\");\n return DEFAULT_ICON;\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { DynamicAppIconRegistry } from "./types";
2
+ type IconName = DynamicAppIconRegistry["IconName"];
3
+ export type UseAppIcon = {
4
+ /** Current icon; `null` means the default icon. */
5
+ icon: IconName | null;
6
+ /** Set the icon, or pass `null` to reset to the default. */
7
+ setIcon: (name: IconName | null) => void;
8
+ /** Whether the current icon is the default. */
9
+ isDefault: boolean;
10
+ /** Whether changing the icon is supported on this platform. */
11
+ isSupported: boolean;
12
+ };
13
+ /**
14
+ * React hook for the app's launcher icon. The native module is the source of
15
+ * truth (the value survives the app restart an Android change triggers), so no
16
+ * extra persistence is needed.
17
+ */
18
+ export declare function useAppIcon(): UseAppIcon;
19
+ export {};
20
+ //# sourceMappingURL=useAppIcon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAppIcon.d.ts","sourceRoot":"","sources":["../src/useAppIcon.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEtD,KAAK,QAAQ,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAWnD,MAAM,MAAM,UAAU,GAAG;IACvB,mDAAmD;IACnD,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,4DAA4D;IAC5D,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAuBvC"}
@@ -0,0 +1,40 @@
1
+ import { useCallback, useState } from "react";
2
+ import NativeAppIcon from "./ExpoAppIconChangerModule";
3
+ /**
4
+ * Read the current icon from the native module, normalising the default
5
+ * (`"DEFAULT"`) to `null`.
6
+ */
7
+ function readCurrentIcon() {
8
+ const current = NativeAppIcon.getAppIcon();
9
+ return current === "DEFAULT" ? null : current;
10
+ }
11
+ /**
12
+ * React hook for the app's launcher icon. The native module is the source of
13
+ * truth (the value survives the app restart an Android change triggers), so no
14
+ * extra persistence is needed.
15
+ */
16
+ export function useAppIcon() {
17
+ const [icon, setIconState] = useState(() => {
18
+ try {
19
+ return readCurrentIcon();
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ });
25
+ const setIcon = useCallback((name) => {
26
+ // Defer so any parent re-render (e.g. a menu closing) settles before the
27
+ // native call, which can present a system alert / restart the app.
28
+ setTimeout(() => {
29
+ try {
30
+ NativeAppIcon.setAppIcon(name);
31
+ setIconState(name);
32
+ }
33
+ catch (error) {
34
+ console.error("Failed to set app icon:", error);
35
+ }
36
+ }, 0);
37
+ }, []);
38
+ return { icon, setIcon, isDefault: icon === null, isSupported: true };
39
+ }
40
+ //# sourceMappingURL=useAppIcon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAppIcon.js","sourceRoot":"","sources":["../src/useAppIcon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,aAAa,MAAM,4BAA4B,CAAC;AAKvD;;;GAGG;AACH,SAAS,eAAe;IACtB,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,EAAE,CAAC;IAC3C,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,OAAoB,CAAC;AAC9D,CAAC;AAaD;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAkB,GAAG,EAAE;QAC1D,IAAI,CAAC;YACH,OAAO,eAAe,EAAE,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,IAAqB,EAAE,EAAE;QACpD,yEAAyE;QACzE,mEAAmE;QACnE,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,CAAC;gBACH,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC/B,YAAY,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC;QACH,CAAC,EAAE,CAAC,CAAC,CAAC;IACR,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,KAAK,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AACxE,CAAC","sourcesContent":["import { useCallback, useState } from \"react\";\n\nimport NativeAppIcon from \"./ExpoAppIconChangerModule\";\nimport type { DynamicAppIconRegistry } from \"./types\";\n\ntype IconName = DynamicAppIconRegistry[\"IconName\"];\n\n/**\n * Read the current icon from the native module, normalising the default\n * (`\"DEFAULT\"`) to `null`.\n */\nfunction readCurrentIcon(): IconName | null {\n const current = NativeAppIcon.getAppIcon();\n return current === \"DEFAULT\" ? null : (current as IconName);\n}\n\nexport type UseAppIcon = {\n /** Current icon; `null` means the default icon. */\n icon: IconName | null;\n /** Set the icon, or pass `null` to reset to the default. */\n setIcon: (name: IconName | null) => void;\n /** Whether the current icon is the default. */\n isDefault: boolean;\n /** Whether changing the icon is supported on this platform. */\n isSupported: boolean;\n};\n\n/**\n * React hook for the app's launcher icon. The native module is the source of\n * truth (the value survives the app restart an Android change triggers), so no\n * extra persistence is needed.\n */\nexport function useAppIcon(): UseAppIcon {\n const [icon, setIconState] = useState<IconName | null>(() => {\n try {\n return readCurrentIcon();\n } catch {\n return null;\n }\n });\n\n const setIcon = useCallback((name: IconName | null) => {\n // Defer so any parent re-render (e.g. a menu closing) settles before the\n // native call, which can present a system alert / restart the app.\n setTimeout(() => {\n try {\n NativeAppIcon.setAppIcon(name);\n setIconState(name);\n } catch (error) {\n console.error(\"Failed to set app icon:\", error);\n }\n }, 0);\n }, []);\n\n return { icon, setIcon, isDefault: icon === null, isSupported: true };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-app-icon",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Programmatically change the app icon at runtime in Expo, with Android adaptive-icon support.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -74,7 +74,6 @@ const withAppleAlternateIcons = (config, props) => {
74
74
  return;
75
75
  const entry = {
76
76
  CFBundleIconFiles: [(0, icons_1.appleIconBaseName)(iconKey, variant)],
77
- UIPrerenderedIcon: !!icon.prerendered,
78
77
  };
79
78
  if (variant.target) {
80
79
  (iconsByTarget[variant.target] ??= {})[iconKey] = entry;
@@ -2,7 +2,9 @@ import type { AppleIconVariant, IconConfig, IconPluginInput, IconSet, ResolvedIc
2
2
  /**
3
3
  * Coerce whatever the user placed in app config into a normalized icon map.
4
4
  * A bare array of image paths is expanded to keyed entries that share the same
5
- * image for both platforms.
5
+ * image for both platforms. For the object form, the `image` convenience is
6
+ * expanded to `ios`/`android` (which override it) and `metadata` is carried
7
+ * through untouched.
6
8
  */
7
9
  export declare function normalizeIconSet(input: IconPluginInput): IconSet;
8
10
  /**
@@ -14,7 +14,9 @@ const APPLE_ICON_BLUEPRINTS = [
14
14
  /**
15
15
  * Coerce whatever the user placed in app config into a normalized icon map.
16
16
  * A bare array of image paths is expanded to keyed entries that share the same
17
- * image for both platforms.
17
+ * image for both platforms. For the object form, the `image` convenience is
18
+ * expanded to `ios`/`android` (which override it) and `metadata` is carried
19
+ * through untouched.
18
20
  */
19
21
  function normalizeIconSet(input) {
20
22
  if (Array.isArray(input)) {
@@ -25,11 +27,19 @@ function normalizeIconSet(input) {
25
27
  }
26
28
  if (!input)
27
29
  return {};
28
- return Object.fromEntries(Object.entries(input).map(([key, value]) => [
29
- key,
30
+ return Object.fromEntries(Object.entries(input).map(([key, value]) => {
30
31
  // String shorthand: one image path used for both platforms.
31
- typeof value === "string" ? { ios: value, android: value } : value,
32
- ]));
32
+ if (typeof value === "string") {
33
+ return [key, { ios: value, android: value }];
34
+ }
35
+ const { image, ios, android, metadata } = value;
36
+ const config = {
37
+ ios: ios ?? image,
38
+ android: android ?? image,
39
+ ...(metadata ? { metadata } : {}),
40
+ };
41
+ return [key, config];
42
+ }));
33
43
  }
34
44
  /**
35
45
  * Build the list of Apple icon variants to emit for the given tablet support.
@@ -17,7 +17,7 @@ const withDynamicIcon = (config, input) => {
17
17
  const icons = (0, icons_1.normalizeIconSet)(input);
18
18
  const variants = (0, icons_1.resolveAppleVariants)(Boolean(config.ios?.supportsTablet));
19
19
  const props = { icons, variants };
20
- config = withTypedIconNames(config, icons);
20
+ config = withGeneratedIconData(config, icons);
21
21
  config = (0, apple_1.withAppleIconAssets)(config, props);
22
22
  config = (0, apple_1.withAppleAlternateIcons)(config, props);
23
23
  config = (0, apple_1.withAppleIconImages)(config, props);
@@ -26,13 +26,17 @@ const withDynamicIcon = (config, input) => {
26
26
  return config;
27
27
  };
28
28
  /**
29
- * Rewrite the shipped `IconName` union in `build/types.d.ts` to the configured
30
- * icon keys, so `getAppIcon()` / `setAppIcon()` are typed to this project.
29
+ * Sync the shipped package to this project's icons:
30
+ * - rewrite the `IconName` union in `build/types.d.ts` so `getAppIcon()` /
31
+ * `setAppIcon()` / `getAvailableIcons()` are typed to the configured keys, and
32
+ * - emit the `{ name, metadata }` list into `build/icons-data.generated.js` so
33
+ * `getAvailableIcons()` returns it at runtime.
34
+ *
35
+ * Both files only exist in a built package, so writes are best-effort.
31
36
  */
32
- function withTypedIconNames(config, icons) {
33
- const union = Object.keys(icons)
34
- .map((name) => `"${name}"`)
35
- .join(" | ") || "string";
37
+ function withGeneratedIconData(config, icons) {
38
+ const names = Object.keys(icons);
39
+ const union = names.map((name) => `"${name}"`).join(" | ") || "string";
36
40
  const typesFile = path_1.default.join(PACKAGE_ROOT, "build", "types.d.ts");
37
41
  try {
38
42
  const current = fs_1.default.readFileSync(typesFile, "utf8");
@@ -41,6 +45,17 @@ function withTypedIconNames(config, icons) {
41
45
  catch {
42
46
  // The types file only exists in a built package; ignore when absent.
43
47
  }
48
+ const iconData = names.map((name) => ({
49
+ name,
50
+ metadata: icons[name]?.metadata ?? {},
51
+ }));
52
+ const dataFile = path_1.default.join(PACKAGE_ROOT, "build", "icons-data.generated.js");
53
+ try {
54
+ fs_1.default.writeFileSync(dataFile, `export const ICON_DATA = ${JSON.stringify(iconData)};\n`);
55
+ }
56
+ catch {
57
+ // The build output only exists in a built package; ignore when absent.
58
+ }
44
59
  return config;
45
60
  }
46
61
  exports.default = withDynamicIcon;
@@ -1,5 +1,11 @@
1
1
  /**
2
- * A single icon entry as declared in the plugin config.
2
+ * Arbitrary, JSON-serialisable metadata a consumer attaches to an icon (e.g.
3
+ * label, description, isPremium). Surfaced at runtime via getAvailableIcons().
4
+ */
5
+ export type IconMetadata = Record<string, unknown>;
6
+ /**
7
+ * A normalized icon entry as used everywhere internally (the `image`
8
+ * convenience has been expanded to `ios`/`android`).
3
9
  */
4
10
  export type IconConfig = {
5
11
  /**
@@ -11,20 +17,28 @@ export type IconConfig = {
11
17
  */
12
18
  android?: string;
13
19
  /**
14
- * Whether iOS should treat the icon as already rendered (no gloss).
20
+ * Display/behaviour metadata, passed through to runtime untouched.
15
21
  */
16
- prerendered?: boolean;
22
+ metadata?: IconMetadata;
17
23
  };
18
24
  /**
19
25
  * Map of icon key → icon config, as used everywhere internally (always the
20
26
  * fully-resolved object form).
21
27
  */
22
28
  export type IconSet = Record<string, IconConfig>;
29
+ /**
30
+ * The object form a consumer may declare. Adds `image` as a convenience that
31
+ * sets both platforms (`ios`/`android` override it when present).
32
+ */
33
+ export type IconInputObject = IconConfig & {
34
+ /** Single image used for both platforms (shorthand for ios + android). */
35
+ image?: string;
36
+ };
23
37
  /**
24
38
  * An icon as declared by the user: either a single image path used for both
25
- * platforms (`"red": "./assets/red.png"`) or the explicit per-platform object.
39
+ * platforms (`"red": "./assets/red.png"`) or the explicit object form.
26
40
  */
27
- export type IconInput = string | IconConfig;
41
+ export type IconInput = string | IconInputObject;
28
42
  /**
29
43
  * What the plugin accepts from the app config: a keyed map (string shorthand or
30
44
  * object per icon), a bare list of image paths, or nothing.