my fork of the bluesky client

[Subs] Custom app icons (#6758)

* custom icons

* rm default

* clouds.jpg

* use cross-platform fork

* minor fixes for android

* update dynamic icon lib

* gate app icon settings behind discover debug dids

* rename clouds

* Bop it

* Update default ios icon as well

* Remove old icon

* Update logo placement

* update to latest expo-dynamic-app-icon

* fix android icon sizes

---------

Co-authored-by: Eric Bailey <git@esb.lol>

authored by samuel.fm Eric Bailey and committed by GitHub 6a29296a c7d57b63

+69 -2
app.config.js
··· 71 71 policy: 'appVersion', 72 72 }, 73 73 orientation: 'portrait', 74 - icon: './assets/icon.png', 74 + icon: './assets/app-icons/ios_icon_default_light.png', 75 75 userInterfaceStyle: 'automatic', 76 76 splash: SPLASH_CONFIG, 77 77 // hsl(211, 99%, 53%), same as palette.default.brandText ··· 162 162 backgroundColor: DARK_SPLASH_CONFIG_ANDROID.backgroundColor, 163 163 }, 164 164 android: { 165 - icon: './assets/icon.png', 165 + icon: './assets/app-icons/android_icon_default_light.png', 166 166 adaptiveIcon: { 167 167 foregroundImage: './assets/icon-android-foreground.png', 168 168 monochromeImage: './assets/icon-android-foreground.png', ··· 271 271 './assets/fonts/inter/Inter-ExtraBold.otf', 272 272 './assets/fonts/inter/Inter-ExtraBoldItalic.otf', 273 273 ], 274 + }, 275 + ], 276 + [ 277 + '@mozzius/expo-dynamic-app-icon', 278 + { 279 + /** 280 + * Default set 281 + */ 282 + default_light: { 283 + ios: './assets/app-icons/ios_icon_default_light.png', 284 + android: './assets/app-icons/android_icon_default_light.png', 285 + prerendered: true, 286 + }, 287 + default_dark: { 288 + ios: './assets/app-icons/ios_icon_default_dark.png', 289 + android: './assets/app-icons/android_icon_default_dark.png', 290 + prerendered: true, 291 + }, 292 + 293 + /** 294 + * Bluesky+ core set 295 + */ 296 + core_aurora: { 297 + ios: './assets/app-icons/ios_icon_core_aurora.png', 298 + android: './assets/app-icons/android_icon_core_aurora.png', 299 + prerendered: true, 300 + }, 301 + core_bonfire: { 302 + ios: './assets/app-icons/ios_icon_core_bonfire.png', 303 + android: './assets/app-icons/android_icon_core_bonfire.png', 304 + prerendered: true, 305 + }, 306 + core_sunrise: { 307 + ios: './assets/app-icons/ios_icon_core_sunrise.png', 308 + android: './assets/app-icons/android_icon_core_sunrise.png', 309 + prerendered: true, 310 + }, 311 + core_sunset: { 312 + ios: './assets/app-icons/ios_icon_core_sunset.png', 313 + android: './assets/app-icons/android_icon_core_sunset.png', 314 + prerendered: true, 315 + }, 316 + core_midnight: { 317 + ios: './assets/app-icons/ios_icon_core_midnight.png', 318 + android: './assets/app-icons/android_icon_core_midnight.png', 319 + prerendered: true, 320 + }, 321 + core_flat_blue: { 322 + ios: './assets/app-icons/ios_icon_core_flat_blue.png', 323 + android: './assets/app-icons/android_icon_core_flat_blue.png', 324 + prerendered: true, 325 + }, 326 + core_flat_white: { 327 + ios: './assets/app-icons/ios_icon_core_flat_white.png', 328 + android: './assets/app-icons/android_icon_core_flat_white.png', 329 + prerendered: true, 330 + }, 331 + core_flat_black: { 332 + ios: './assets/app-icons/ios_icon_core_flat_black.png', 333 + android: './assets/app-icons/android_icon_core_flat_black.png', 334 + prerendered: true, 335 + }, 336 + core_classic: { 337 + ios: './assets/app-icons/ios_icon_core_classic.png', 338 + android: './assets/app-icons/android_icon_core_classic.png', 339 + prerendered: true, 340 + }, 274 341 }, 275 342 ], 276 343 ].filter(Boolean),
assets/app-icons/android_icon_core_aurora.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_bonfire.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_classic.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_flat_black.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_flat_blue.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_flat_white.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_midnight.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_sunrise.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_core_sunset.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_default_dark.png

This is a binary file and will not be displayed.

assets/app-icons/android_icon_default_light.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_aurora.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_bonfire.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_classic.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_flat_black.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_flat_blue.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_flat_white.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_midnight.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_sunrise.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_core_sunset.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_default_dark.png

This is a binary file and will not be displayed.

assets/app-icons/ios_icon_default_light.png

This is a binary file and will not be displayed.

assets/icon.png

This is a binary file and will not be displayed.

+1
bskyweb/cmd/bskyweb/server.go
··· 257 257 e.GET("/settings/privacy-and-security", server.WebGeneric) 258 258 e.GET("/settings/content-and-media", server.WebGeneric) 259 259 e.GET("/settings/about", server.WebGeneric) 260 + e.GET("/settings/app-icon", server.WebGeneric) 260 261 e.GET("/sys/debug", server.WebGeneric) 261 262 e.GET("/sys/debug-mod", server.WebGeneric) 262 263 e.GET("/sys/log", server.WebGeneric)
+1
package.json
··· 74 74 "@lingui/react": "^4.5.0", 75 75 "@mattermost/react-native-paste-input": "^0.7.1", 76 76 "@miblanchard/react-native-slider": "^2.3.1", 77 + "@mozzius/expo-dynamic-app-icon": "^1.4.1", 77 78 "@radix-ui/react-dismissable-layer": "^1.1.1", 78 79 "@radix-ui/react-dropdown-menu": "2.0.1", 79 80 "@radix-ui/react-focus-guards": "^1.1.1",
+9
src/Navigation.tsx
··· 79 79 import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers' 80 80 import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' 81 81 import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings' 82 + import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings' 82 83 import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings' 83 84 import { 84 85 StarterPackScreen, ··· 359 360 getComponent={() => AboutSettingsScreen} 360 361 options={{ 361 362 title: title(msg`About`), 363 + requireAuth: true, 364 + }} 365 + /> 366 + <Stack.Screen 367 + name="AppIconSettings" 368 + getComponent={() => AppIconSettingsScreen} 369 + options={{ 370 + title: title(msg`App Icon`), 362 371 requireAuth: true, 363 372 }} 364 373 />
+7 -1
src/alf/atoms.ts
··· 1 1 import {Platform, StyleProp, StyleSheet, ViewStyle} from 'react-native' 2 2 3 3 import * as tokens from '#/alf/tokens' 4 - import {native, web} from '#/alf/util/platform' 4 + import {ios, native, web} from '#/alf/util/platform' 5 5 6 6 export const atoms = { 7 7 debug: { ··· 312 312 border_r: { 313 313 borderRightWidth: StyleSheet.hairlineWidth, 314 314 }, 315 + curve_circular: ios({ 316 + borderCurve: 'circular', 317 + }), 318 + curve_continuous: ios({ 319 + borderCurve: 'continuous', 320 + }), 315 321 316 322 /* 317 323 * Shadow
+1
src/lib/constants.ts
··· 29 29 'did:plc:p2cp5gopk7mgjegy6wadk3ep': true, // samuel.bsky.team 30 30 'did:plc:ragtjsm2j2vknwkz3zp4oxrd': true, // pfrazee.com 31 31 'did:plc:vpkhqolt662uhesyj6nxm7ys': true, // why.bsky.team 32 + 'did:plc:3jpt2mvvsumj2r7eqk4gzzjz': true, // esb.lol 32 33 } 33 34 34 35 const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
+1
src/lib/routes/types.ts
··· 44 44 PrivacyAndSecuritySettings: undefined 45 45 ContentAndMediaSettings: undefined 46 46 AboutSettings: undefined 47 + AppIconSettings: undefined 47 48 Search: {q?: string} 48 49 Hashtag: {tag: string; author?: string} 49 50 MessagesConversation: {conversation: string; embed?: string}
+1
src/routes.ts
··· 44 44 PrivacyAndSecuritySettings: '/settings/privacy-and-security', 45 45 ContentAndMediaSettings: '/settings/content-and-media', 46 46 AboutSettings: '/settings/about', 47 + AppIconSettings: '/settings/app-icon', 47 48 // support 48 49 Support: '/support', 49 50 PrivacyPolicy: '/support/privacy',
+252
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} 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 title={_('App Icon')} /> 24 + <Layout.Content 25 + contentContainerStyle={[a.py_2xl, a.px_xl, {paddingBottom: 100}]}> 26 + <Text style={[a.text_lg, a.font_heavy]}>Defaults</Text> 27 + <View style={[a.flex_row, a.flex_wrap]}> 28 + {sets.defaults.map(icon => ( 29 + <View 30 + style={[{width: '50%'}, a.py_lg, a.px_xs, a.align_center]} 31 + key={icon.id}> 32 + <PressableScale 33 + accessibilityLabel={icon.name} 34 + accessibilityHint={_(msg`Tap to change app icon`)} 35 + targetScale={0.95} 36 + onPress={() => AppIcon.setAppIcon(icon.id)}> 37 + <Image 38 + source={platform({ 39 + ios: icon.iosImage(), 40 + android: icon.androidImage(), 41 + })} 42 + style={[ 43 + {width: 100, height: 100}, 44 + platform({ 45 + ios: {borderRadius: 20}, 46 + android: a.rounded_full, 47 + }), 48 + a.curve_continuous, 49 + ]} 50 + accessibilityIgnoresInvertColors 51 + /> 52 + </PressableScale> 53 + <Text style={[a.text_center, a.font_bold, a.text_md, a.mt_md]}> 54 + {icon.name} 55 + </Text> 56 + </View> 57 + ))} 58 + </View> 59 + 60 + <Text style={[a.text_lg, a.font_heavy]}>Bluesky+</Text> 61 + <View style={[a.flex_row, a.flex_wrap]}> 62 + {sets.core.map(icon => ( 63 + <View 64 + style={[{width: '50%'}, a.py_lg, a.px_xs, a.align_center]} 65 + key={icon.id}> 66 + <PressableScale 67 + accessibilityLabel={icon.name} 68 + accessibilityHint={_(msg`Tap to change app icon`)} 69 + targetScale={0.95} 70 + onPress={() => { 71 + if (isAndroid) { 72 + Alert.alert( 73 + _(msg`Change app icon to "${icon.name}"`), 74 + _(msg`The app will be restarted`), 75 + [ 76 + { 77 + text: _(msg`Cancel`), 78 + style: 'cancel', 79 + }, 80 + { 81 + text: _(msg`OK`), 82 + onPress: () => { 83 + AppIcon.setAppIcon(icon.id) 84 + }, 85 + style: 'default', 86 + }, 87 + ], 88 + ) 89 + } else { 90 + AppIcon.setAppIcon(icon.id) 91 + } 92 + }}> 93 + <Image 94 + source={platform({ 95 + ios: icon.iosImage(), 96 + android: icon.androidImage(), 97 + })} 98 + style={[ 99 + {width: 100, height: 100}, 100 + platform({ 101 + ios: {borderRadius: 20}, 102 + android: a.rounded_full, 103 + }), 104 + a.curve_continuous, 105 + a.shadow_lg, 106 + ]} 107 + accessibilityIgnoresInvertColors 108 + /> 109 + </PressableScale> 110 + <Text 111 + style={[a.text_center, a.font_bold, a.text_md, a.mt_md]} 112 + // for Classic™ 113 + emoji> 114 + {icon.name} 115 + </Text> 116 + </View> 117 + ))} 118 + </View> 119 + </Layout.Content> 120 + </Layout.Screen> 121 + ) 122 + } 123 + 124 + function useAppIconSets() { 125 + const {_} = useLingui() 126 + 127 + return React.useMemo(() => { 128 + const defaults = [ 129 + { 130 + id: 'default_light', 131 + name: _('Light'), 132 + iosImage: () => { 133 + return require(`../../../assets/app-icons/ios_icon_default_light.png`) 134 + }, 135 + androidImage: () => { 136 + return require(`../../../assets/app-icons/android_icon_default_light.png`) 137 + }, 138 + }, 139 + { 140 + id: 'default_dark', 141 + name: _('Dark'), 142 + iosImage: () => { 143 + return require(`../../../assets/app-icons/ios_icon_default_dark.png`) 144 + }, 145 + androidImage: () => { 146 + return require(`../../../assets/app-icons/android_icon_default_dark.png`) 147 + }, 148 + }, 149 + ] 150 + 151 + /** 152 + * Bluesky+ 153 + */ 154 + const core = [ 155 + { 156 + id: 'core_aurora', 157 + name: _('Aurora'), 158 + iosImage: () => { 159 + return require(`../../../assets/app-icons/ios_icon_core_aurora.png`) 160 + }, 161 + androidImage: () => { 162 + return require(`../../../assets/app-icons/android_icon_core_aurora.png`) 163 + }, 164 + }, 165 + // { 166 + // id: 'core_bonfire', 167 + // name: _('Bonfire'), 168 + // iosImage: () => { 169 + // return require(`../../../assets/app-icons/ios_icon_core_bonfire.png`) 170 + // }, 171 + // androidImage: () => { 172 + // return require(`../../../assets/app-icons/android_icon_core_bonfire.png`) 173 + // }, 174 + // }, 175 + { 176 + id: 'core_sunrise', 177 + name: _('Sunrise'), 178 + iosImage: () => { 179 + return require(`../../../assets/app-icons/ios_icon_core_sunrise.png`) 180 + }, 181 + androidImage: () => { 182 + return require(`../../../assets/app-icons/android_icon_core_sunrise.png`) 183 + }, 184 + }, 185 + { 186 + id: 'core_sunset', 187 + name: _('Sunset'), 188 + iosImage: () => { 189 + return require(`../../../assets/app-icons/ios_icon_core_sunset.png`) 190 + }, 191 + androidImage: () => { 192 + return require(`../../../assets/app-icons/android_icon_core_sunset.png`) 193 + }, 194 + }, 195 + { 196 + id: 'core_midnight', 197 + name: _('Midnight'), 198 + iosImage: () => { 199 + return require(`../../../assets/app-icons/ios_icon_core_midnight.png`) 200 + }, 201 + androidImage: () => { 202 + return require(`../../../assets/app-icons/android_icon_core_midnight.png`) 203 + }, 204 + }, 205 + { 206 + id: 'core_flat_blue', 207 + name: _('Flat Blue'), 208 + iosImage: () => { 209 + return require(`../../../assets/app-icons/ios_icon_core_flat_blue.png`) 210 + }, 211 + androidImage: () => { 212 + return require(`../../../assets/app-icons/android_icon_core_flat_blue.png`) 213 + }, 214 + }, 215 + { 216 + id: 'core_flat_white', 217 + name: _('Flat White'), 218 + iosImage: () => { 219 + return require(`../../../assets/app-icons/ios_icon_core_flat_white.png`) 220 + }, 221 + androidImage: () => { 222 + return require(`../../../assets/app-icons/android_icon_core_flat_white.png`) 223 + }, 224 + }, 225 + { 226 + id: 'core_flat_black', 227 + name: _('Flat Black'), 228 + iosImage: () => { 229 + return require(`../../../assets/app-icons/ios_icon_core_flat_black.png`) 230 + }, 231 + androidImage: () => { 232 + return require(`../../../assets/app-icons/android_icon_core_flat_black.png`) 233 + }, 234 + }, 235 + { 236 + id: 'core_classic', 237 + name: _('Bluesky Classic™'), 238 + iosImage: () => { 239 + return require(`../../../assets/app-icons/ios_icon_core_classic.png`) 240 + }, 241 + androidImage: () => { 242 + return require(`../../../assets/app-icons/android_icon_core_classic.png`) 243 + }, 244 + }, 245 + ] 246 + 247 + return { 248 + defaults, 249 + core, 250 + } 251 + }, [_]) 252 + }
+23 -1
src/screens/Settings/AppearanceSettings.tsx
··· 5 5 LayoutAnimationConfig, 6 6 LinearTransition, 7 7 } from 'react-native-reanimated' 8 - import {msg} from '@lingui/macro' 8 + import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 10 11 + import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' 11 12 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 13 + import {useSession} from '#/state/session' 12 14 import {useSetThemePrefs, useThemePrefs} from '#/state/shell' 15 + import {Logo} from '#/view/icons/Logo' 13 16 import {atoms as a, native, useAlf, useTheme} from '#/alf' 14 17 import * as ToggleButton from '#/components/forms/ToggleButton' 15 18 import {Props as SVGIconProps} from '#/components/icons/common' ··· 70 73 [fonts], 71 74 ) 72 75 76 + const {currentAccount} = useSession() 77 + 73 78 return ( 74 79 <LayoutAnimationConfig skipExiting skipEntering> 75 80 <Layout.Screen testID="preferencesThreadsScreen"> ··· 121 126 )} 122 127 123 128 <Animated.View layout={native(LinearTransition)}> 129 + <SettingsList.Divider /> 130 + 124 131 <AppearanceToggleButtonGroup 125 132 title={_(msg`Font`)} 126 133 description={_( ··· 161 168 values={[fonts.scale]} 162 169 onChange={onChangeFontScale} 163 170 /> 171 + 172 + {DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( 173 + <> 174 + <SettingsList.Divider /> 175 + 176 + <SettingsList.LinkItem 177 + to="/settings/app-icon" 178 + label={_(msg`App Icon`)}> 179 + <SettingsList.ItemIcon icon={Logo} /> 180 + <SettingsList.ItemText> 181 + <Trans>App Icon</Trans> 182 + </SettingsList.ItemText> 183 + </SettingsList.LinkItem> 184 + </> 185 + )} 164 186 </Animated.View> 165 187 </SettingsList.Container> 166 188 </Layout.Content>
+17 -1
yarn.lock
··· 3736 3736 resolved "https://registry.yarnpkg.com/@expo/html-elements/-/html-elements-0.4.3.tgz#32b4ca05dd13582164ed1be34ae87e22adfd1d5b" 3737 3737 integrity sha512-UwEEdnpyhUEIDe/AkFSBUmCuwcknjAuu73fd5L9Rm/BbHczYXCrtyZmzCNVBsAiHhwUjmhNWzFlr9cAkp/sxIA== 3738 3738 3739 - "@expo/image-utils@0.3.23": 3739 + "@expo/image-utils@0.3.23", "@expo/image-utils@^0.3.23": 3740 3740 version "0.3.23" 3741 3741 resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.23.tgz#f14fd7e1f5ff6f8e4911a41e27dd274470665c3f" 3742 3742 integrity sha512-nhUVvW0TrRE4jtWzHQl8TR4ox7kcmrc2I0itaeJGjxF5A54uk7avgA0wRt7jP1rdvqQo1Ke1lXyLYREdhN9tPw== ··· 4936 4936 version "2.3.1" 4937 4937 resolved "https://registry.yarnpkg.com/@miblanchard/react-native-slider/-/react-native-slider-2.3.1.tgz#79e0f1f9b1ce43ef25ee51ee9256c012e5dfa412" 4938 4938 integrity sha512-J/hZDBWmXq8fJeOnTVHqIUVDHshqMSpJVxJ4WqwuCBKl5Rke9OBYXIdkSlgi75OgtScAr8FKK5KNkDKHUf6JIg== 4939 + 4940 + "@mozzius/expo-dynamic-app-icon@^1.4.1": 4941 + version "1.4.1" 4942 + resolved "https://registry.yarnpkg.com/@mozzius/expo-dynamic-app-icon/-/expo-dynamic-app-icon-1.4.1.tgz#245e54c31347e3ec2a1ce10f0df8cf07a0c1be6e" 4943 + integrity sha512-IiL6OiuW4kP5Jz/vrZ6U1t0m4gK1rW5VZAQzszdVcZy1cadX3EdR2/uA6jMU0qSwuesk028RhO6S0uBI9ckxBw== 4944 + dependencies: 4945 + "@expo/image-utils" "^0.3.23" 4946 + expo-modules-core "^1.0.3" 4947 + xcode "^3.0.1" 4939 4948 4940 4949 "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": 4941 4950 version "5.1.1-v1" ··· 10660 10669 version "1.12.11" 10661 10670 resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.12.11.tgz#71d7efb2f6a2a4d3b96defad52fc799b9804f829" 10662 10671 integrity sha512-CF5G6hZo/6uIUz6tj4dNRlvE5L4lakYukXPqz5ZHQ+6fLk1NQVZbRdpHjMkxO/QSBQcKUzG/ngeytpoJus7poQ== 10672 + dependencies: 10673 + invariant "^2.2.4" 10674 + 10675 + expo-modules-core@^1.0.3: 10676 + version "1.12.26" 10677 + resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.12.26.tgz#86c4087dc6246abfc4d7f5e61097dc8cc4b22262" 10678 + integrity sha512-y8yDWjOi+rQRdO+HY+LnUlz8qzHerUaw/LUjKPU/mX8PRXP4UUPEEp5fjAwBU44xjNmYSHWZDwet4IBBE+yQUA== 10663 10679 dependencies: 10664 10680 invariant "^2.2.4" 10665 10681