Bluesky app fork with some witchin' additions 💫

Nicer app icon screen (#6972)

* wip exploration

* list format instead of grid

* fix normalising default name

* adjust margins

* Rework the app icon link

* Decrease app icon size

* Adjust some spacing

* Move some things around to fix web errors

* Fix pathname

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: Eric Bailey <git@esb.lol>

authored by samuel.fm

Paul Frazee
Eric Bailey
and committed by
GitHub
e49dad28 69f22b9d

+478 -270
-260
src/screens/Settings/AppIconSettings.tsx
··· 1 - import React from 'react' 2 - import {Alert, View} from 'react-native' 3 - import {Image} from 'expo-image' 4 - import {msg, Trans} from '@lingui/macro' 5 - import {useLingui} from '@lingui/react' 6 - import * as AppIcon from '@mozzius/expo-dynamic-app-icon' 7 - import {NativeStackScreenProps} from '@react-navigation/native-stack' 8 - 9 - import {PressableScale} from '#/lib/custom-animations/PressableScale' 10 - import {CommonNavigatorParams} from '#/lib/routes/types' 11 - import {isAndroid} from '#/platform/detection' 12 - import {atoms as a, platform} from '#/alf' 13 - import * as Layout from '#/components/Layout' 14 - import {Text} from '#/components/Typography' 15 - 16 - type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'> 17 - export function AppIconSettingsScreen({}: Props) { 18 - const {_} = useLingui() 19 - const sets = useAppIconSets() 20 - 21 - return ( 22 - <Layout.Screen> 23 - <Layout.Header.Outer> 24 - <Layout.Header.BackButton /> 25 - <Layout.Header.Content> 26 - <Layout.Header.TitleText> 27 - <Trans>App Icon</Trans> 28 - </Layout.Header.TitleText> 29 - </Layout.Header.Content> 30 - <Layout.Header.Slot /> 31 - </Layout.Header.Outer> 32 - <Layout.Content 33 - contentContainerStyle={[a.py_2xl, a.px_xl, {paddingBottom: 100}]}> 34 - <Text style={[a.text_lg, a.font_heavy]}>Defaults</Text> 35 - <View style={[a.flex_row, a.flex_wrap]}> 36 - {sets.defaults.map(icon => ( 37 - <View 38 - style={[{width: '50%'}, a.py_lg, a.px_xs, a.align_center]} 39 - key={icon.id}> 40 - <PressableScale 41 - accessibilityLabel={icon.name} 42 - accessibilityHint={_(msg`Tap to change app icon`)} 43 - targetScale={0.95} 44 - onPress={() => AppIcon.setAppIcon(icon.id)}> 45 - <Image 46 - source={platform({ 47 - ios: icon.iosImage(), 48 - android: icon.androidImage(), 49 - })} 50 - style={[ 51 - {width: 100, height: 100}, 52 - platform({ 53 - ios: {borderRadius: 20}, 54 - android: a.rounded_full, 55 - }), 56 - a.curve_continuous, 57 - ]} 58 - accessibilityIgnoresInvertColors 59 - /> 60 - </PressableScale> 61 - <Text style={[a.text_center, a.font_bold, a.text_md, a.mt_md]}> 62 - {icon.name} 63 - </Text> 64 - </View> 65 - ))} 66 - </View> 67 - 68 - <Text style={[a.text_lg, a.font_heavy]}>Bluesky+</Text> 69 - <View style={[a.flex_row, a.flex_wrap]}> 70 - {sets.core.map(icon => ( 71 - <View 72 - style={[{width: '50%'}, a.py_lg, a.px_xs, a.align_center]} 73 - key={icon.id}> 74 - <PressableScale 75 - accessibilityLabel={icon.name} 76 - accessibilityHint={_(msg`Tap to change app icon`)} 77 - targetScale={0.95} 78 - onPress={() => { 79 - if (isAndroid) { 80 - Alert.alert( 81 - _(msg`Change app icon to "${icon.name}"`), 82 - _(msg`The app will be restarted`), 83 - [ 84 - { 85 - text: _(msg`Cancel`), 86 - style: 'cancel', 87 - }, 88 - { 89 - text: _(msg`OK`), 90 - onPress: () => { 91 - AppIcon.setAppIcon(icon.id) 92 - }, 93 - style: 'default', 94 - }, 95 - ], 96 - ) 97 - } else { 98 - AppIcon.setAppIcon(icon.id) 99 - } 100 - }}> 101 - <Image 102 - source={platform({ 103 - ios: icon.iosImage(), 104 - android: icon.androidImage(), 105 - })} 106 - style={[ 107 - {width: 100, height: 100}, 108 - platform({ 109 - ios: {borderRadius: 20}, 110 - android: a.rounded_full, 111 - }), 112 - a.curve_continuous, 113 - a.shadow_lg, 114 - ]} 115 - accessibilityIgnoresInvertColors 116 - /> 117 - </PressableScale> 118 - <Text 119 - style={[a.text_center, a.font_bold, a.text_md, a.mt_md]} 120 - // for Classic™ 121 - emoji> 122 - {icon.name} 123 - </Text> 124 - </View> 125 - ))} 126 - </View> 127 - </Layout.Content> 128 - </Layout.Screen> 129 - ) 130 - } 131 - 132 - function useAppIconSets() { 133 - const {_} = useLingui() 134 - 135 - return React.useMemo(() => { 136 - const defaults = [ 137 - { 138 - id: 'default_light', 139 - name: _('Light'), 140 - iosImage: () => { 141 - return require(`../../../assets/app-icons/ios_icon_default_light.png`) 142 - }, 143 - androidImage: () => { 144 - return require(`../../../assets/app-icons/android_icon_default_light.png`) 145 - }, 146 - }, 147 - { 148 - id: 'default_dark', 149 - name: _('Dark'), 150 - iosImage: () => { 151 - return require(`../../../assets/app-icons/ios_icon_default_dark.png`) 152 - }, 153 - androidImage: () => { 154 - return require(`../../../assets/app-icons/android_icon_default_dark.png`) 155 - }, 156 - }, 157 - ] 158 - 159 - /** 160 - * Bluesky+ 161 - */ 162 - const core = [ 163 - { 164 - id: 'core_aurora', 165 - name: _('Aurora'), 166 - iosImage: () => { 167 - return require(`../../../assets/app-icons/ios_icon_core_aurora.png`) 168 - }, 169 - androidImage: () => { 170 - return require(`../../../assets/app-icons/android_icon_core_aurora.png`) 171 - }, 172 - }, 173 - // { 174 - // id: 'core_bonfire', 175 - // name: _('Bonfire'), 176 - // iosImage: () => { 177 - // return require(`../../../assets/app-icons/ios_icon_core_bonfire.png`) 178 - // }, 179 - // androidImage: () => { 180 - // return require(`../../../assets/app-icons/android_icon_core_bonfire.png`) 181 - // }, 182 - // }, 183 - { 184 - id: 'core_sunrise', 185 - name: _('Sunrise'), 186 - iosImage: () => { 187 - return require(`../../../assets/app-icons/ios_icon_core_sunrise.png`) 188 - }, 189 - androidImage: () => { 190 - return require(`../../../assets/app-icons/android_icon_core_sunrise.png`) 191 - }, 192 - }, 193 - { 194 - id: 'core_sunset', 195 - name: _('Sunset'), 196 - iosImage: () => { 197 - return require(`../../../assets/app-icons/ios_icon_core_sunset.png`) 198 - }, 199 - androidImage: () => { 200 - return require(`../../../assets/app-icons/android_icon_core_sunset.png`) 201 - }, 202 - }, 203 - { 204 - id: 'core_midnight', 205 - name: _('Midnight'), 206 - iosImage: () => { 207 - return require(`../../../assets/app-icons/ios_icon_core_midnight.png`) 208 - }, 209 - androidImage: () => { 210 - return require(`../../../assets/app-icons/android_icon_core_midnight.png`) 211 - }, 212 - }, 213 - { 214 - id: 'core_flat_blue', 215 - name: _('Flat Blue'), 216 - iosImage: () => { 217 - return require(`../../../assets/app-icons/ios_icon_core_flat_blue.png`) 218 - }, 219 - androidImage: () => { 220 - return require(`../../../assets/app-icons/android_icon_core_flat_blue.png`) 221 - }, 222 - }, 223 - { 224 - id: 'core_flat_white', 225 - name: _('Flat White'), 226 - iosImage: () => { 227 - return require(`../../../assets/app-icons/ios_icon_core_flat_white.png`) 228 - }, 229 - androidImage: () => { 230 - return require(`../../../assets/app-icons/android_icon_core_flat_white.png`) 231 - }, 232 - }, 233 - { 234 - id: 'core_flat_black', 235 - name: _('Flat Black'), 236 - iosImage: () => { 237 - return require(`../../../assets/app-icons/ios_icon_core_flat_black.png`) 238 - }, 239 - androidImage: () => { 240 - return require(`../../../assets/app-icons/android_icon_core_flat_black.png`) 241 - }, 242 - }, 243 - { 244 - id: 'core_classic', 245 - name: _('Bluesky Classic™'), 246 - iosImage: () => { 247 - return require(`../../../assets/app-icons/ios_icon_core_classic.png`) 248 - }, 249 - androidImage: () => { 250 - return require(`../../../assets/app-icons/android_icon_core_classic.png`) 251 - }, 252 - }, 253 - ] 254 - 255 - return { 256 - defaults, 257 - core, 258 - } 259 - }, [_]) 260 - }
src/screens/Settings/AppIconSettings.web.tsx src/screens/Settings/AppIconSettings/index.web.tsx
+33
src/screens/Settings/AppIconSettings/AppIconImage.tsx
··· 1 + import {Image} from 'expo-image' 2 + 3 + import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' 4 + import {atoms as a, platform, useTheme} from '#/alf' 5 + 6 + export function AppIconImage({ 7 + icon, 8 + size = 50, 9 + }: { 10 + icon: AppIconSet 11 + size: number 12 + }) { 13 + const t = useTheme() 14 + return ( 15 + <Image 16 + source={platform({ 17 + ios: icon.iosImage(), 18 + android: icon.androidImage(), 19 + })} 20 + style={[ 21 + {width: size, height: size}, 22 + platform({ 23 + ios: {borderRadius: size / 5}, 24 + android: a.rounded_full, 25 + }), 26 + a.curve_continuous, 27 + t.atoms.border_contrast_medium, 28 + a.border, 29 + ]} 30 + accessibilityIgnoresInvertColors 31 + /> 32 + ) 33 + }
+29
src/screens/Settings/AppIconSettings/SettingsListItem.tsx
··· 1 + import {View} from 'react-native' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + 5 + import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' 6 + import {useCurrentAppIcon} from '#/screens/Settings/AppIconSettings/useCurrentAppIcon' 7 + import * as SettingsList from '#/screens/Settings/components/SettingsList' 8 + import {atoms as a} from '#/alf' 9 + import {Shapes_Stroke2_Corner0_Rounded as Shapes} from '#/components/icons/Shapes' 10 + 11 + export function SettingsListItem() { 12 + const {_} = useLingui() 13 + const icon = useCurrentAppIcon() 14 + 15 + return ( 16 + <SettingsList.LinkItem 17 + to="/settings/app-icon" 18 + label={_(msg`App Icon`)} 19 + contentContainerStyle={[a.align_start]}> 20 + <SettingsList.ItemIcon icon={Shapes} /> 21 + <View style={[a.flex_1]}> 22 + <SettingsList.ItemText style={[a.pt_xs, a.pb_md]}> 23 + <Trans>App Icon</Trans> 24 + </SettingsList.ItemText> 25 + <AppIconImage icon={icon} size={60} /> 26 + </View> 27 + </SettingsList.LinkItem> 28 + ) 29 + }
+1
src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx
··· 1 + export function SettingsListItem() {}
+244
src/screens/Settings/AppIconSettings/index.tsx
··· 1 + import {useState} from 'react' 2 + import {Alert, View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' 6 + import {NativeStackScreenProps} from '@react-navigation/native-stack' 7 + 8 + import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' 9 + import {PressableScale} from '#/lib/custom-animations/PressableScale' 10 + import {CommonNavigatorParams} from '#/lib/routes/types' 11 + import {isAndroid} from '#/platform/detection' 12 + import {useSession} from '#/state/session' 13 + import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' 14 + import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' 15 + import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' 16 + import {atoms as a, useTheme} from '#/alf' 17 + import * as Toggle from '#/components/forms/Toggle' 18 + import * as Layout from '#/components/Layout' 19 + import {Text} from '#/components/Typography' 20 + 21 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'> 22 + export function AppIconSettingsScreen({}: Props) { 23 + const t = useTheme() 24 + const {_} = useLingui() 25 + const sets = useAppIconSets() 26 + const {currentAccount} = useSession() 27 + const [currentAppIcon, setCurrentAppIcon] = useState(() => 28 + getAppIconName(DynamicAppIcon.getAppIcon()), 29 + ) 30 + 31 + const onSetAppIcon = (icon: string) => { 32 + if (isAndroid) { 33 + const next = 34 + sets.defaults.find(i => i.id === icon) ?? 35 + sets.core.find(i => i.id === icon) 36 + Alert.alert( 37 + next 38 + ? _(msg`Change app icon to "${next.name}"`) 39 + : _(msg`Change app icon`), 40 + // to determine - can we stop this happening? -sfn 41 + _(msg`The app will be restarted`), 42 + [ 43 + { 44 + text: _(msg`Cancel`), 45 + style: 'cancel', 46 + }, 47 + { 48 + text: _(msg`OK`), 49 + onPress: () => { 50 + setCurrentAppIcon(setAppIcon(icon)) 51 + }, 52 + style: 'default', 53 + }, 54 + ], 55 + ) 56 + } else { 57 + setCurrentAppIcon(setAppIcon(icon)) 58 + } 59 + } 60 + 61 + return ( 62 + <Layout.Screen> 63 + <Layout.Header.Outer> 64 + <Layout.Header.BackButton /> 65 + <Layout.Header.Content> 66 + <Layout.Header.TitleText> 67 + <Trans>App Icon</Trans> 68 + </Layout.Header.TitleText> 69 + </Layout.Header.Content> 70 + <Layout.Header.Slot /> 71 + </Layout.Header.Outer> 72 + 73 + <Layout.Content contentContainerStyle={[a.p_lg]}> 74 + <Group 75 + label={_(msg`Default icons`)} 76 + value={currentAppIcon} 77 + onChange={onSetAppIcon}> 78 + {sets.defaults.map((icon, i) => ( 79 + <Row 80 + key={icon.id} 81 + icon={icon} 82 + isEnd={i === sets.defaults.length - 1}> 83 + <AppIcon icon={icon} key={icon.id} size={40} /> 84 + <RowText>{icon.name}</RowText> 85 + </Row> 86 + ))} 87 + </Group> 88 + 89 + {DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( 90 + <> 91 + <Text 92 + style={[ 93 + a.text_md, 94 + a.mt_xl, 95 + a.mb_sm, 96 + a.font_bold, 97 + t.atoms.text_contrast_medium, 98 + ]}> 99 + <Trans>Bluesky+</Trans> 100 + </Text> 101 + <Group 102 + label={_(msg`Bluesky+ icons`)} 103 + value={currentAppIcon} 104 + onChange={onSetAppIcon}> 105 + {sets.core.map((icon, i) => ( 106 + <Row 107 + key={icon.id} 108 + icon={icon} 109 + isEnd={i === sets.core.length - 1}> 110 + <AppIcon icon={icon} key={icon.id} size={40} /> 111 + <RowText>{icon.name}</RowText> 112 + </Row> 113 + ))} 114 + </Group> 115 + </> 116 + )} 117 + </Layout.Content> 118 + </Layout.Screen> 119 + ) 120 + } 121 + 122 + function setAppIcon(icon: string) { 123 + if (icon === 'default_light') { 124 + return getAppIconName(DynamicAppIcon.setAppIcon(null)) 125 + } else { 126 + return getAppIconName(DynamicAppIcon.setAppIcon(icon)) 127 + } 128 + } 129 + 130 + function getAppIconName(icon: string | false) { 131 + if (!icon || icon === 'DEFAULT') { 132 + return 'default_light' 133 + } else { 134 + return icon 135 + } 136 + } 137 + 138 + function Group({ 139 + children, 140 + label, 141 + value, 142 + onChange, 143 + }: { 144 + children: React.ReactNode 145 + label: string 146 + value: string 147 + onChange: (value: string) => void 148 + }) { 149 + return ( 150 + <Toggle.Group 151 + type="radio" 152 + label={label} 153 + values={[value]} 154 + maxSelections={1} 155 + onChange={vals => { 156 + if (vals[0]) onChange(vals[0]) 157 + }}> 158 + <View style={[a.flex_1, a.rounded_md, a.overflow_hidden]}> 159 + {children} 160 + </View> 161 + </Toggle.Group> 162 + ) 163 + } 164 + 165 + function Row({ 166 + icon, 167 + children, 168 + isEnd, 169 + }: { 170 + icon: AppIconSet 171 + children: React.ReactNode 172 + isEnd: boolean 173 + }) { 174 + const t = useTheme() 175 + const {_} = useLingui() 176 + 177 + return ( 178 + <Toggle.Item label={_(msg`Set app icon to ${icon.name}`)} name={icon.id}> 179 + {({hovered, pressed}) => ( 180 + <View 181 + style={[ 182 + a.flex_1, 183 + a.p_md, 184 + a.flex_row, 185 + a.gap_md, 186 + a.align_center, 187 + t.atoms.bg_contrast_25, 188 + (hovered || pressed) && t.atoms.bg_contrast_50, 189 + t.atoms.border_contrast_high, 190 + !isEnd && a.border_b, 191 + ]}> 192 + {children} 193 + <Toggle.Radio /> 194 + </View> 195 + )} 196 + </Toggle.Item> 197 + ) 198 + } 199 + 200 + function RowText({children}: {children: React.ReactNode}) { 201 + const t = useTheme() 202 + return ( 203 + <Text 204 + style={[a.text_md, a.font_bold, a.flex_1, t.atoms.text_contrast_medium]} 205 + emoji> 206 + {children} 207 + </Text> 208 + ) 209 + } 210 + 211 + function AppIcon({icon, size = 50}: {icon: AppIconSet; size: number}) { 212 + const {_} = useLingui() 213 + return ( 214 + <PressableScale 215 + accessibilityLabel={icon.name} 216 + accessibilityHint={_(msg`Tap to change app icon`)} 217 + targetScale={0.95} 218 + onPress={() => { 219 + if (isAndroid) { 220 + Alert.alert( 221 + _(msg`Change app icon to "${icon.name}"`), 222 + _(msg`The app will be restarted`), 223 + [ 224 + { 225 + text: _(msg`Cancel`), 226 + style: 'cancel', 227 + }, 228 + { 229 + text: _(msg`OK`), 230 + onPress: () => { 231 + DynamicAppIcon.setAppIcon(icon.id) 232 + }, 233 + style: 'default', 234 + }, 235 + ], 236 + ) 237 + } else { 238 + DynamicAppIcon.setAppIcon(icon.id) 239 + } 240 + }}> 241 + <AppIconImage icon={icon} size={size} /> 242 + </PressableScale> 243 + ) 244 + }
+8
src/screens/Settings/AppIconSettings/types.ts
··· 1 + import {ImageSourcePropType} from 'react-native' 2 + 3 + export type AppIconSet = { 4 + id: string 5 + name: string 6 + iosImage: () => ImageSourcePropType 7 + androidImage: () => ImageSourcePropType 8 + }
+134
src/screens/Settings/AppIconSettings/useAppIconSets.ts
··· 1 + import {useMemo} from 'react' 2 + import {useLingui} from '@lingui/react' 3 + 4 + import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' 5 + 6 + export function useAppIconSets() { 7 + const {_} = useLingui() 8 + 9 + return useMemo(() => { 10 + const defaults = [ 11 + { 12 + id: 'default_light', 13 + name: _('Light'), 14 + iosImage: () => { 15 + return require(`../../../../assets/app-icons/ios_icon_default_light.png`) 16 + }, 17 + androidImage: () => { 18 + return require(`../../../../assets/app-icons/android_icon_default_light.png`) 19 + }, 20 + }, 21 + { 22 + id: 'default_dark', 23 + name: _('Dark'), 24 + iosImage: () => { 25 + return require(`../../../../assets/app-icons/ios_icon_default_dark.png`) 26 + }, 27 + androidImage: () => { 28 + return require(`../../../../assets/app-icons/android_icon_default_dark.png`) 29 + }, 30 + }, 31 + ] satisfies AppIconSet[] 32 + 33 + /** 34 + * Bluesky+ 35 + */ 36 + const core = [ 37 + { 38 + id: 'core_aurora', 39 + name: _('Aurora'), 40 + iosImage: () => { 41 + return require(`../../../../assets/app-icons/ios_icon_core_aurora.png`) 42 + }, 43 + androidImage: () => { 44 + return require(`../../../../assets/app-icons/android_icon_core_aurora.png`) 45 + }, 46 + }, 47 + // { 48 + // id: 'core_bonfire', 49 + // name: _('Bonfire'), 50 + // iosImage: () => { 51 + // return require(`../../../../assets/app-icons/ios_icon_core_bonfire.png`) 52 + // }, 53 + // androidImage: () => { 54 + // return require(`../../../../assets/app-icons/android_icon_core_bonfire.png`) 55 + // }, 56 + // }, 57 + { 58 + id: 'core_sunrise', 59 + name: _('Sunrise'), 60 + iosImage: () => { 61 + return require(`../../../../assets/app-icons/ios_icon_core_sunrise.png`) 62 + }, 63 + androidImage: () => { 64 + return require(`../../../../assets/app-icons/android_icon_core_sunrise.png`) 65 + }, 66 + }, 67 + { 68 + id: 'core_sunset', 69 + name: _('Sunset'), 70 + iosImage: () => { 71 + return require(`../../../../assets/app-icons/ios_icon_core_sunset.png`) 72 + }, 73 + androidImage: () => { 74 + return require(`../../../../assets/app-icons/android_icon_core_sunset.png`) 75 + }, 76 + }, 77 + { 78 + id: 'core_midnight', 79 + name: _('Midnight'), 80 + iosImage: () => { 81 + return require(`../../../../assets/app-icons/ios_icon_core_midnight.png`) 82 + }, 83 + androidImage: () => { 84 + return require(`../../../../assets/app-icons/android_icon_core_midnight.png`) 85 + }, 86 + }, 87 + { 88 + id: 'core_flat_blue', 89 + name: _('Flat Blue'), 90 + iosImage: () => { 91 + return require(`../../../../assets/app-icons/ios_icon_core_flat_blue.png`) 92 + }, 93 + androidImage: () => { 94 + return require(`../../../../assets/app-icons/android_icon_core_flat_blue.png`) 95 + }, 96 + }, 97 + { 98 + id: 'core_flat_white', 99 + name: _('Flat White'), 100 + iosImage: () => { 101 + return require(`../../../../assets/app-icons/ios_icon_core_flat_white.png`) 102 + }, 103 + androidImage: () => { 104 + return require(`../../../../assets/app-icons/android_icon_core_flat_white.png`) 105 + }, 106 + }, 107 + { 108 + id: 'core_flat_black', 109 + name: _('Flat Black'), 110 + iosImage: () => { 111 + return require(`../../../../assets/app-icons/ios_icon_core_flat_black.png`) 112 + }, 113 + androidImage: () => { 114 + return require(`../../../../assets/app-icons/android_icon_core_flat_black.png`) 115 + }, 116 + }, 117 + { 118 + id: 'core_classic', 119 + name: _('Bluesky Classic™'), 120 + iosImage: () => { 121 + return require(`../../../../assets/app-icons/ios_icon_core_classic.png`) 122 + }, 123 + androidImage: () => { 124 + return require(`../../../../assets/app-icons/android_icon_core_classic.png`) 125 + }, 126 + }, 127 + ] satisfies AppIconSet[] 128 + 129 + return { 130 + defaults, 131 + core, 132 + } 133 + }, [_]) 134 + }
+27
src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts
··· 1 + import {useCallback, useMemo, useState} from 'react' 2 + import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' 3 + import {useFocusEffect} from '@react-navigation/native' 4 + 5 + import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' 6 + 7 + export function useCurrentAppIcon() { 8 + const appIconSets = useAppIconSets() 9 + const [currentAppIcon, setCurrentAppIcon] = useState(() => 10 + DynamicAppIcon.getAppIcon(), 11 + ) 12 + 13 + // refresh current icon when screen is focused 14 + useFocusEffect( 15 + useCallback(() => { 16 + setCurrentAppIcon(DynamicAppIcon.getAppIcon()) 17 + }, []), 18 + ) 19 + 20 + return useMemo(() => { 21 + return ( 22 + appIconSets.defaults.find(i => i.id === currentAppIcon) ?? 23 + appIconSets.core.find(i => i.id === currentAppIcon) ?? 24 + appIconSets.defaults[0] 25 + ) 26 + }, [appIconSets, currentAppIcon]) 27 + }
+2 -10
src/screens/Settings/AppearanceSettings.tsx
··· 13 13 import {isNative} from '#/platform/detection' 14 14 import {useSession} from '#/state/session' 15 15 import {useSetThemePrefs, useThemePrefs} from '#/state/shell' 16 - import {Logo} from '#/view/icons/Logo' 16 + import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem' 17 17 import {atoms as a, native, useAlf, useTheme} from '#/alf' 18 18 import * as ToggleButton from '#/components/forms/ToggleButton' 19 19 import {Props as SVGIconProps} from '#/components/icons/common' ··· 181 181 {isNative && DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( 182 182 <> 183 183 <SettingsList.Divider /> 184 - 185 - <SettingsList.LinkItem 186 - to="/settings/app-icon" 187 - label={_(msg`App Icon`)}> 188 - <SettingsList.ItemIcon icon={Logo} /> 189 - <SettingsList.ItemText> 190 - <Trans>App Icon</Trans> 191 - </SettingsList.ItemText> 192 - </SettingsList.LinkItem> 184 + <AppIconSettingsListItem /> 193 185 </> 194 186 )} 195 187 </Animated.View>