about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/screens/Settings/AppIconSettings.tsx260
-rw-r--r--src/screens/Settings/AppIconSettings/AppIconImage.tsx33
-rw-r--r--src/screens/Settings/AppIconSettings/SettingsListItem.tsx29
-rw-r--r--src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx1
-rw-r--r--src/screens/Settings/AppIconSettings/index.tsx244
-rw-r--r--src/screens/Settings/AppIconSettings/index.web.tsx (renamed from src/screens/Settings/AppIconSettings.web.tsx)0
-rw-r--r--src/screens/Settings/AppIconSettings/types.ts8
-rw-r--r--src/screens/Settings/AppIconSettings/useAppIconSets.ts134
-rw-r--r--src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts27
-rw-r--r--src/screens/Settings/AppearanceSettings.tsx12
10 files changed, 478 insertions, 270 deletions
diff --git a/src/screens/Settings/AppIconSettings.tsx b/src/screens/Settings/AppIconSettings.tsx
deleted file mode 100644
index 18fcd5e30..000000000
--- a/src/screens/Settings/AppIconSettings.tsx
+++ /dev/null
@@ -1,260 +0,0 @@
-import React from 'react'
-import {Alert, View} from 'react-native'
-import {Image} from 'expo-image'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import * as AppIcon from '@mozzius/expo-dynamic-app-icon'
-import {NativeStackScreenProps} from '@react-navigation/native-stack'
-
-import {PressableScale} from '#/lib/custom-animations/PressableScale'
-import {CommonNavigatorParams} from '#/lib/routes/types'
-import {isAndroid} from '#/platform/detection'
-import {atoms as a, platform} from '#/alf'
-import * as Layout from '#/components/Layout'
-import {Text} from '#/components/Typography'
-
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'>
-export function AppIconSettingsScreen({}: Props) {
-  const {_} = useLingui()
-  const sets = useAppIconSets()
-
-  return (
-    <Layout.Screen>
-      <Layout.Header.Outer>
-        <Layout.Header.BackButton />
-        <Layout.Header.Content>
-          <Layout.Header.TitleText>
-            <Trans>App Icon</Trans>
-          </Layout.Header.TitleText>
-        </Layout.Header.Content>
-        <Layout.Header.Slot />
-      </Layout.Header.Outer>
-      <Layout.Content
-        contentContainerStyle={[a.py_2xl, a.px_xl, {paddingBottom: 100}]}>
-        <Text style={[a.text_lg, a.font_heavy]}>Defaults</Text>
-        <View style={[a.flex_row, a.flex_wrap]}>
-          {sets.defaults.map(icon => (
-            <View
-              style={[{width: '50%'}, a.py_lg, a.px_xs, a.align_center]}
-              key={icon.id}>
-              <PressableScale
-                accessibilityLabel={icon.name}
-                accessibilityHint={_(msg`Tap to change app icon`)}
-                targetScale={0.95}
-                onPress={() => AppIcon.setAppIcon(icon.id)}>
-                <Image
-                  source={platform({
-                    ios: icon.iosImage(),
-                    android: icon.androidImage(),
-                  })}
-                  style={[
-                    {width: 100, height: 100},
-                    platform({
-                      ios: {borderRadius: 20},
-                      android: a.rounded_full,
-                    }),
-                    a.curve_continuous,
-                  ]}
-                  accessibilityIgnoresInvertColors
-                />
-              </PressableScale>
-              <Text style={[a.text_center, a.font_bold, a.text_md, a.mt_md]}>
-                {icon.name}
-              </Text>
-            </View>
-          ))}
-        </View>
-
-        <Text style={[a.text_lg, a.font_heavy]}>Bluesky+</Text>
-        <View style={[a.flex_row, a.flex_wrap]}>
-          {sets.core.map(icon => (
-            <View
-              style={[{width: '50%'}, a.py_lg, a.px_xs, a.align_center]}
-              key={icon.id}>
-              <PressableScale
-                accessibilityLabel={icon.name}
-                accessibilityHint={_(msg`Tap to change app icon`)}
-                targetScale={0.95}
-                onPress={() => {
-                  if (isAndroid) {
-                    Alert.alert(
-                      _(msg`Change app icon to "${icon.name}"`),
-                      _(msg`The app will be restarted`),
-                      [
-                        {
-                          text: _(msg`Cancel`),
-                          style: 'cancel',
-                        },
-                        {
-                          text: _(msg`OK`),
-                          onPress: () => {
-                            AppIcon.setAppIcon(icon.id)
-                          },
-                          style: 'default',
-                        },
-                      ],
-                    )
-                  } else {
-                    AppIcon.setAppIcon(icon.id)
-                  }
-                }}>
-                <Image
-                  source={platform({
-                    ios: icon.iosImage(),
-                    android: icon.androidImage(),
-                  })}
-                  style={[
-                    {width: 100, height: 100},
-                    platform({
-                      ios: {borderRadius: 20},
-                      android: a.rounded_full,
-                    }),
-                    a.curve_continuous,
-                    a.shadow_lg,
-                  ]}
-                  accessibilityIgnoresInvertColors
-                />
-              </PressableScale>
-              <Text
-                style={[a.text_center, a.font_bold, a.text_md, a.mt_md]}
-                // for Classicâ„¢
-                emoji>
-                {icon.name}
-              </Text>
-            </View>
-          ))}
-        </View>
-      </Layout.Content>
-    </Layout.Screen>
-  )
-}
-
-function useAppIconSets() {
-  const {_} = useLingui()
-
-  return React.useMemo(() => {
-    const defaults = [
-      {
-        id: 'default_light',
-        name: _('Light'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_default_light.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_default_light.png`)
-        },
-      },
-      {
-        id: 'default_dark',
-        name: _('Dark'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_default_dark.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_default_dark.png`)
-        },
-      },
-    ]
-
-    /**
-     * Bluesky+
-     */
-    const core = [
-      {
-        id: 'core_aurora',
-        name: _('Aurora'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_aurora.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_aurora.png`)
-        },
-      },
-      // {
-      //   id: 'core_bonfire',
-      //   name: _('Bonfire'),
-      //   iosImage: () => {
-      //     return require(`../../../assets/app-icons/ios_icon_core_bonfire.png`)
-      //   },
-      //   androidImage: () => {
-      //     return require(`../../../assets/app-icons/android_icon_core_bonfire.png`)
-      //   },
-      // },
-      {
-        id: 'core_sunrise',
-        name: _('Sunrise'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_sunrise.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_sunrise.png`)
-        },
-      },
-      {
-        id: 'core_sunset',
-        name: _('Sunset'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_sunset.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_sunset.png`)
-        },
-      },
-      {
-        id: 'core_midnight',
-        name: _('Midnight'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_midnight.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_midnight.png`)
-        },
-      },
-      {
-        id: 'core_flat_blue',
-        name: _('Flat Blue'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_flat_blue.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_flat_blue.png`)
-        },
-      },
-      {
-        id: 'core_flat_white',
-        name: _('Flat White'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_flat_white.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_flat_white.png`)
-        },
-      },
-      {
-        id: 'core_flat_black',
-        name: _('Flat Black'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_flat_black.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_flat_black.png`)
-        },
-      },
-      {
-        id: 'core_classic',
-        name: _('Bluesky Classicâ„¢'),
-        iosImage: () => {
-          return require(`../../../assets/app-icons/ios_icon_core_classic.png`)
-        },
-        androidImage: () => {
-          return require(`../../../assets/app-icons/android_icon_core_classic.png`)
-        },
-      },
-    ]
-
-    return {
-      defaults,
-      core,
-    }
-  }, [_])
-}
diff --git a/src/screens/Settings/AppIconSettings/AppIconImage.tsx b/src/screens/Settings/AppIconSettings/AppIconImage.tsx
new file mode 100644
index 000000000..e81d5d0d5
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings/AppIconImage.tsx
@@ -0,0 +1,33 @@
+import {Image} from 'expo-image'
+
+import {AppIconSet} from '#/screens/Settings/AppIconSettings/types'
+import {atoms as a, platform, useTheme} from '#/alf'
+
+export function AppIconImage({
+  icon,
+  size = 50,
+}: {
+  icon: AppIconSet
+  size: number
+}) {
+  const t = useTheme()
+  return (
+    <Image
+      source={platform({
+        ios: icon.iosImage(),
+        android: icon.androidImage(),
+      })}
+      style={[
+        {width: size, height: size},
+        platform({
+          ios: {borderRadius: size / 5},
+          android: a.rounded_full,
+        }),
+        a.curve_continuous,
+        t.atoms.border_contrast_medium,
+        a.border,
+      ]}
+      accessibilityIgnoresInvertColors
+    />
+  )
+}
diff --git a/src/screens/Settings/AppIconSettings/SettingsListItem.tsx b/src/screens/Settings/AppIconSettings/SettingsListItem.tsx
new file mode 100644
index 000000000..add87b1d7
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings/SettingsListItem.tsx
@@ -0,0 +1,29 @@
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage'
+import {useCurrentAppIcon} from '#/screens/Settings/AppIconSettings/useCurrentAppIcon'
+import * as SettingsList from '#/screens/Settings/components/SettingsList'
+import {atoms as a} from '#/alf'
+import {Shapes_Stroke2_Corner0_Rounded as Shapes} from '#/components/icons/Shapes'
+
+export function SettingsListItem() {
+  const {_} = useLingui()
+  const icon = useCurrentAppIcon()
+
+  return (
+    <SettingsList.LinkItem
+      to="/settings/app-icon"
+      label={_(msg`App Icon`)}
+      contentContainerStyle={[a.align_start]}>
+      <SettingsList.ItemIcon icon={Shapes} />
+      <View style={[a.flex_1]}>
+        <SettingsList.ItemText style={[a.pt_xs, a.pb_md]}>
+          <Trans>App Icon</Trans>
+        </SettingsList.ItemText>
+        <AppIconImage icon={icon} size={60} />
+      </View>
+    </SettingsList.LinkItem>
+  )
+}
diff --git a/src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx b/src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx
new file mode 100644
index 000000000..c7707d23f
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx
@@ -0,0 +1 @@
+export function SettingsListItem() {}
diff --git a/src/screens/Settings/AppIconSettings/index.tsx b/src/screens/Settings/AppIconSettings/index.tsx
new file mode 100644
index 000000000..0fefca29b
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings/index.tsx
@@ -0,0 +1,244 @@
+import {useState} from 'react'
+import {Alert, View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon'
+import {NativeStackScreenProps} from '@react-navigation/native-stack'
+
+import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
+import {PressableScale} from '#/lib/custom-animations/PressableScale'
+import {CommonNavigatorParams} from '#/lib/routes/types'
+import {isAndroid} from '#/platform/detection'
+import {useSession} from '#/state/session'
+import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage'
+import {AppIconSet} from '#/screens/Settings/AppIconSettings/types'
+import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets'
+import {atoms as a, useTheme} from '#/alf'
+import * as Toggle from '#/components/forms/Toggle'
+import * as Layout from '#/components/Layout'
+import {Text} from '#/components/Typography'
+
+type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'>
+export function AppIconSettingsScreen({}: Props) {
+  const t = useTheme()
+  const {_} = useLingui()
+  const sets = useAppIconSets()
+  const {currentAccount} = useSession()
+  const [currentAppIcon, setCurrentAppIcon] = useState(() =>
+    getAppIconName(DynamicAppIcon.getAppIcon()),
+  )
+
+  const onSetAppIcon = (icon: string) => {
+    if (isAndroid) {
+      const next =
+        sets.defaults.find(i => i.id === icon) ??
+        sets.core.find(i => i.id === icon)
+      Alert.alert(
+        next
+          ? _(msg`Change app icon to "${next.name}"`)
+          : _(msg`Change app icon`),
+        // to determine - can we stop this happening? -sfn
+        _(msg`The app will be restarted`),
+        [
+          {
+            text: _(msg`Cancel`),
+            style: 'cancel',
+          },
+          {
+            text: _(msg`OK`),
+            onPress: () => {
+              setCurrentAppIcon(setAppIcon(icon))
+            },
+            style: 'default',
+          },
+        ],
+      )
+    } else {
+      setCurrentAppIcon(setAppIcon(icon))
+    }
+  }
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>App Icon</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot />
+      </Layout.Header.Outer>
+
+      <Layout.Content contentContainerStyle={[a.p_lg]}>
+        <Group
+          label={_(msg`Default icons`)}
+          value={currentAppIcon}
+          onChange={onSetAppIcon}>
+          {sets.defaults.map((icon, i) => (
+            <Row
+              key={icon.id}
+              icon={icon}
+              isEnd={i === sets.defaults.length - 1}>
+              <AppIcon icon={icon} key={icon.id} size={40} />
+              <RowText>{icon.name}</RowText>
+            </Row>
+          ))}
+        </Group>
+
+        {DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && (
+          <>
+            <Text
+              style={[
+                a.text_md,
+                a.mt_xl,
+                a.mb_sm,
+                a.font_bold,
+                t.atoms.text_contrast_medium,
+              ]}>
+              <Trans>Bluesky+</Trans>
+            </Text>
+            <Group
+              label={_(msg`Bluesky+ icons`)}
+              value={currentAppIcon}
+              onChange={onSetAppIcon}>
+              {sets.core.map((icon, i) => (
+                <Row
+                  key={icon.id}
+                  icon={icon}
+                  isEnd={i === sets.core.length - 1}>
+                  <AppIcon icon={icon} key={icon.id} size={40} />
+                  <RowText>{icon.name}</RowText>
+                </Row>
+              ))}
+            </Group>
+          </>
+        )}
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
+
+function setAppIcon(icon: string) {
+  if (icon === 'default_light') {
+    return getAppIconName(DynamicAppIcon.setAppIcon(null))
+  } else {
+    return getAppIconName(DynamicAppIcon.setAppIcon(icon))
+  }
+}
+
+function getAppIconName(icon: string | false) {
+  if (!icon || icon === 'DEFAULT') {
+    return 'default_light'
+  } else {
+    return icon
+  }
+}
+
+function Group({
+  children,
+  label,
+  value,
+  onChange,
+}: {
+  children: React.ReactNode
+  label: string
+  value: string
+  onChange: (value: string) => void
+}) {
+  return (
+    <Toggle.Group
+      type="radio"
+      label={label}
+      values={[value]}
+      maxSelections={1}
+      onChange={vals => {
+        if (vals[0]) onChange(vals[0])
+      }}>
+      <View style={[a.flex_1, a.rounded_md, a.overflow_hidden]}>
+        {children}
+      </View>
+    </Toggle.Group>
+  )
+}
+
+function Row({
+  icon,
+  children,
+  isEnd,
+}: {
+  icon: AppIconSet
+  children: React.ReactNode
+  isEnd: boolean
+}) {
+  const t = useTheme()
+  const {_} = useLingui()
+
+  return (
+    <Toggle.Item label={_(msg`Set app icon to ${icon.name}`)} name={icon.id}>
+      {({hovered, pressed}) => (
+        <View
+          style={[
+            a.flex_1,
+            a.p_md,
+            a.flex_row,
+            a.gap_md,
+            a.align_center,
+            t.atoms.bg_contrast_25,
+            (hovered || pressed) && t.atoms.bg_contrast_50,
+            t.atoms.border_contrast_high,
+            !isEnd && a.border_b,
+          ]}>
+          {children}
+          <Toggle.Radio />
+        </View>
+      )}
+    </Toggle.Item>
+  )
+}
+
+function RowText({children}: {children: React.ReactNode}) {
+  const t = useTheme()
+  return (
+    <Text
+      style={[a.text_md, a.font_bold, a.flex_1, t.atoms.text_contrast_medium]}
+      emoji>
+      {children}
+    </Text>
+  )
+}
+
+function AppIcon({icon, size = 50}: {icon: AppIconSet; size: number}) {
+  const {_} = useLingui()
+  return (
+    <PressableScale
+      accessibilityLabel={icon.name}
+      accessibilityHint={_(msg`Tap to change app icon`)}
+      targetScale={0.95}
+      onPress={() => {
+        if (isAndroid) {
+          Alert.alert(
+            _(msg`Change app icon to "${icon.name}"`),
+            _(msg`The app will be restarted`),
+            [
+              {
+                text: _(msg`Cancel`),
+                style: 'cancel',
+              },
+              {
+                text: _(msg`OK`),
+                onPress: () => {
+                  DynamicAppIcon.setAppIcon(icon.id)
+                },
+                style: 'default',
+              },
+            ],
+          )
+        } else {
+          DynamicAppIcon.setAppIcon(icon.id)
+        }
+      }}>
+      <AppIconImage icon={icon} size={size} />
+    </PressableScale>
+  )
+}
diff --git a/src/screens/Settings/AppIconSettings.web.tsx b/src/screens/Settings/AppIconSettings/index.web.tsx
index d8c4d9bea..d8c4d9bea 100644
--- a/src/screens/Settings/AppIconSettings.web.tsx
+++ b/src/screens/Settings/AppIconSettings/index.web.tsx
diff --git a/src/screens/Settings/AppIconSettings/types.ts b/src/screens/Settings/AppIconSettings/types.ts
new file mode 100644
index 000000000..5010f6f02
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings/types.ts
@@ -0,0 +1,8 @@
+import {ImageSourcePropType} from 'react-native'
+
+export type AppIconSet = {
+  id: string
+  name: string
+  iosImage: () => ImageSourcePropType
+  androidImage: () => ImageSourcePropType
+}
diff --git a/src/screens/Settings/AppIconSettings/useAppIconSets.ts b/src/screens/Settings/AppIconSettings/useAppIconSets.ts
new file mode 100644
index 000000000..47fc5a15f
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings/useAppIconSets.ts
@@ -0,0 +1,134 @@
+import {useMemo} from 'react'
+import {useLingui} from '@lingui/react'
+
+import {AppIconSet} from '#/screens/Settings/AppIconSettings/types'
+
+export function useAppIconSets() {
+  const {_} = useLingui()
+
+  return useMemo(() => {
+    const defaults = [
+      {
+        id: 'default_light',
+        name: _('Light'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_default_light.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_default_light.png`)
+        },
+      },
+      {
+        id: 'default_dark',
+        name: _('Dark'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_default_dark.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_default_dark.png`)
+        },
+      },
+    ] satisfies AppIconSet[]
+
+    /**
+     * Bluesky+
+     */
+    const core = [
+      {
+        id: 'core_aurora',
+        name: _('Aurora'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_aurora.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_aurora.png`)
+        },
+      },
+      // {
+      //   id: 'core_bonfire',
+      //   name: _('Bonfire'),
+      //   iosImage: () => {
+      //     return require(`../../../../assets/app-icons/ios_icon_core_bonfire.png`)
+      //   },
+      //   androidImage: () => {
+      //     return require(`../../../../assets/app-icons/android_icon_core_bonfire.png`)
+      //   },
+      // },
+      {
+        id: 'core_sunrise',
+        name: _('Sunrise'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_sunrise.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_sunrise.png`)
+        },
+      },
+      {
+        id: 'core_sunset',
+        name: _('Sunset'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_sunset.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_sunset.png`)
+        },
+      },
+      {
+        id: 'core_midnight',
+        name: _('Midnight'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_midnight.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_midnight.png`)
+        },
+      },
+      {
+        id: 'core_flat_blue',
+        name: _('Flat Blue'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_flat_blue.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_flat_blue.png`)
+        },
+      },
+      {
+        id: 'core_flat_white',
+        name: _('Flat White'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_flat_white.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_flat_white.png`)
+        },
+      },
+      {
+        id: 'core_flat_black',
+        name: _('Flat Black'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_flat_black.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_flat_black.png`)
+        },
+      },
+      {
+        id: 'core_classic',
+        name: _('Bluesky Classicâ„¢'),
+        iosImage: () => {
+          return require(`../../../../assets/app-icons/ios_icon_core_classic.png`)
+        },
+        androidImage: () => {
+          return require(`../../../../assets/app-icons/android_icon_core_classic.png`)
+        },
+      },
+    ] satisfies AppIconSet[]
+
+    return {
+      defaults,
+      core,
+    }
+  }, [_])
+}
diff --git a/src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts b/src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts
new file mode 100644
index 000000000..4bc9b665a
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts
@@ -0,0 +1,27 @@
+import {useCallback, useMemo, useState} from 'react'
+import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon'
+import {useFocusEffect} from '@react-navigation/native'
+
+import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets'
+
+export function useCurrentAppIcon() {
+  const appIconSets = useAppIconSets()
+  const [currentAppIcon, setCurrentAppIcon] = useState(() =>
+    DynamicAppIcon.getAppIcon(),
+  )
+
+  // refresh current icon when screen is focused
+  useFocusEffect(
+    useCallback(() => {
+      setCurrentAppIcon(DynamicAppIcon.getAppIcon())
+    }, []),
+  )
+
+  return useMemo(() => {
+    return (
+      appIconSets.defaults.find(i => i.id === currentAppIcon) ??
+      appIconSets.core.find(i => i.id === currentAppIcon) ??
+      appIconSets.defaults[0]
+    )
+  }, [appIconSets, currentAppIcon])
+}
diff --git a/src/screens/Settings/AppearanceSettings.tsx b/src/screens/Settings/AppearanceSettings.tsx
index 48c4a2d85..81ac59105 100644
--- a/src/screens/Settings/AppearanceSettings.tsx
+++ b/src/screens/Settings/AppearanceSettings.tsx
@@ -13,7 +13,7 @@ import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
 import {isNative} from '#/platform/detection'
 import {useSession} from '#/state/session'
 import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
-import {Logo} from '#/view/icons/Logo'
+import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem'
 import {atoms as a, native, useAlf, useTheme} from '#/alf'
 import * as ToggleButton from '#/components/forms/ToggleButton'
 import {Props as SVGIconProps} from '#/components/icons/common'
@@ -181,15 +181,7 @@ export function AppearanceSettingsScreen({}: Props) {
               {isNative && DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && (
                 <>
                   <SettingsList.Divider />
-
-                  <SettingsList.LinkItem
-                    to="/settings/app-icon"
-                    label={_(msg`App Icon`)}>
-                    <SettingsList.ItemIcon icon={Logo} />
-                    <SettingsList.ItemText>
-                      <Trans>App Icon</Trans>
-                    </SettingsList.ItemText>
-                  </SettingsList.LinkItem>
+                  <AppIconSettingsListItem />
                 </>
               )}
             </Animated.View>