about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Navigation.tsx9
-rw-r--r--src/alf/atoms.ts8
-rw-r--r--src/lib/constants.ts1
-rw-r--r--src/lib/routes/types.ts1
-rw-r--r--src/routes.ts1
-rw-r--r--src/screens/Settings/AppIconSettings.tsx252
-rw-r--r--src/screens/Settings/AppearanceSettings.tsx24
7 files changed, 294 insertions, 2 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index cc815ef70..0b162d363 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -79,6 +79,7 @@ import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy'
 import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers'
 import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
 import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings'
+import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings'
 import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
 import {
   StarterPackScreen,
@@ -363,6 +364,14 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
         }}
       />
       <Stack.Screen
+        name="AppIconSettings"
+        getComponent={() => AppIconSettingsScreen}
+        options={{
+          title: title(msg`App Icon`),
+          requireAuth: true,
+        }}
+      />
+      <Stack.Screen
         name="Hashtag"
         getComponent={() => HashtagScreen}
         options={{title: title(msg`Hashtag`)}}
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index fe8cf9a78..1f08eb7e1 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -1,7 +1,7 @@
 import {Platform, StyleProp, StyleSheet, ViewStyle} from 'react-native'
 
 import * as tokens from '#/alf/tokens'
-import {native, web} from '#/alf/util/platform'
+import {ios, native, web} from '#/alf/util/platform'
 
 export const atoms = {
   debug: {
@@ -312,6 +312,12 @@ export const atoms = {
   border_r: {
     borderRightWidth: StyleSheet.hairlineWidth,
   },
+  curve_circular: ios({
+    borderCurve: 'circular',
+  }),
+  curve_continuous: ios({
+    borderCurve: 'continuous',
+  }),
 
   /*
    * Shadow
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index a44a9e518..ebf4b1ee1 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -29,6 +29,7 @@ export const DISCOVER_DEBUG_DIDS: Record<string, true> = {
   'did:plc:p2cp5gopk7mgjegy6wadk3ep': true, // samuel.bsky.team
   'did:plc:ragtjsm2j2vknwkz3zp4oxrd': true, // pfrazee.com
   'did:plc:vpkhqolt662uhesyj6nxm7ys': true, // why.bsky.team
+  'did:plc:3jpt2mvvsumj2r7eqk4gzzjz': true, // esb.lol
 }
 
 const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index 41a47b4bc..9e3407261 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -44,6 +44,7 @@ export type CommonNavigatorParams = {
   PrivacyAndSecuritySettings: undefined
   ContentAndMediaSettings: undefined
   AboutSettings: undefined
+  AppIconSettings: undefined
   Search: {q?: string}
   Hashtag: {tag: string; author?: string}
   MessagesConversation: {conversation: string; embed?: string}
diff --git a/src/routes.ts b/src/routes.ts
index 388e8c521..188665d84 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -44,6 +44,7 @@ export const router = new Router({
   PrivacyAndSecuritySettings: '/settings/privacy-and-security',
   ContentAndMediaSettings: '/settings/content-and-media',
   AboutSettings: '/settings/about',
+  AppIconSettings: '/settings/app-icon',
   // support
   Support: '/support',
   PrivacyPolicy: '/support/privacy',
diff --git a/src/screens/Settings/AppIconSettings.tsx b/src/screens/Settings/AppIconSettings.tsx
new file mode 100644
index 000000000..1dd87d45f
--- /dev/null
+++ b/src/screens/Settings/AppIconSettings.tsx
@@ -0,0 +1,252 @@
+import React from 'react'
+import {Alert, View} from 'react-native'
+import {Image} from 'expo-image'
+import {msg} 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 title={_('App Icon')} />
+      <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/AppearanceSettings.tsx b/src/screens/Settings/AppearanceSettings.tsx
index d0beb7d50..662b3093f 100644
--- a/src/screens/Settings/AppearanceSettings.tsx
+++ b/src/screens/Settings/AppearanceSettings.tsx
@@ -5,11 +5,14 @@ import Animated, {
   LayoutAnimationConfig,
   LinearTransition,
 } from 'react-native-reanimated'
-import {msg} from '@lingui/macro'
+import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
+import {useSession} from '#/state/session'
 import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
+import {Logo} from '#/view/icons/Logo'
 import {atoms as a, native, useAlf, useTheme} from '#/alf'
 import * as ToggleButton from '#/components/forms/ToggleButton'
 import {Props as SVGIconProps} from '#/components/icons/common'
@@ -70,6 +73,8 @@ export function AppearanceSettingsScreen({}: Props) {
     [fonts],
   )
 
+  const {currentAccount} = useSession()
+
   return (
     <LayoutAnimationConfig skipExiting skipEntering>
       <Layout.Screen testID="preferencesThreadsScreen">
@@ -121,6 +126,8 @@ export function AppearanceSettingsScreen({}: Props) {
             )}
 
             <Animated.View layout={native(LinearTransition)}>
+              <SettingsList.Divider />
+
               <AppearanceToggleButtonGroup
                 title={_(msg`Font`)}
                 description={_(
@@ -161,6 +168,21 @@ export function AppearanceSettingsScreen({}: Props) {
                 values={[fonts.scale]}
                 onChange={onChangeFontScale}
               />
+
+              {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>
+                </>
+              )}
             </Animated.View>
           </SettingsList.Container>
         </Layout.Content>