Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 8c3553cd66ad07ef8c8c4e760b495cf6ce08cc8d 249 lines 6.7 kB view raw
1import {useState} from 'react' 2import {Alert, View} from 'react-native' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' 6import {type NativeStackScreenProps} from '@react-navigation/native-stack' 7 8import {PressableScale} from '#/lib/custom-animations/PressableScale' 9import {type CommonNavigatorParams} from '#/lib/routes/types' 10import {useGate} from '#/lib/statsig/statsig' 11import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' 12import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' 13import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' 14import {atoms as a, useTheme} from '#/alf' 15import * as Toggle from '#/components/forms/Toggle' 16import * as Layout from '#/components/Layout' 17import {Text} from '#/components/Typography' 18import {IS_ANDROID} from '#/env' 19import {IS_INTERNAL} from '#/env' 20 21type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'> 22export function AppIconSettingsScreen({}: Props) { 23 const t = useTheme() 24 const {_} = useLingui() 25 const sets = useAppIconSets() 26 const gate = useGate() 27 const [currentAppIcon, setCurrentAppIcon] = useState(() => 28 getAppIconName(DynamicAppIcon.getAppIcon()), 29 ) 30 31 const onSetAppIcon = (icon: DynamicAppIcon.IconName) => { 32 if (IS_ANDROID) { 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 // unfortunately necessary -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 {IS_INTERNAL && gate('debug_subscriptions') && ( 90 <> 91 <Text 92 style={[ 93 a.text_md, 94 a.mt_xl, 95 a.mb_sm, 96 a.font_semi_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 122function setAppIcon(icon: DynamicAppIcon.IconName) { 123 if (icon === 'default_light') { 124 return getAppIconName(DynamicAppIcon.setAppIcon(null)) 125 } else { 126 return getAppIconName(DynamicAppIcon.setAppIcon(icon)) 127 } 128} 129 130function getAppIconName(icon: string | false): DynamicAppIcon.IconName { 131 if (!icon || icon === 'DEFAULT') { 132 return 'default_light' 133 } else { 134 return icon as DynamicAppIcon.IconName 135 } 136} 137 138function Group({ 139 children, 140 label, 141 value, 142 onChange, 143}: { 144 children: React.ReactNode 145 label: string 146 value: DynamicAppIcon.IconName 147 onChange: (value: DynamicAppIcon.IconName) => 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] as DynamicAppIcon.IconName) 157 }}> 158 <View style={[a.flex_1, a.rounded_md, a.overflow_hidden]}> 159 {children} 160 </View> 161 </Toggle.Group> 162 ) 163} 164 165function 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 200function RowText({children}: {children: React.ReactNode}) { 201 const t = useTheme() 202 return ( 203 <Text 204 style={[ 205 a.text_md, 206 a.font_semi_bold, 207 a.flex_1, 208 t.atoms.text_contrast_medium, 209 ]} 210 emoji> 211 {children} 212 </Text> 213 ) 214} 215 216function AppIcon({icon, size = 50}: {icon: AppIconSet; size: number}) { 217 const {_} = useLingui() 218 return ( 219 <PressableScale 220 accessibilityLabel={icon.name} 221 accessibilityHint={_(msg`Changes app icon`)} 222 targetScale={0.95} 223 onPress={() => { 224 if (IS_ANDROID) { 225 Alert.alert( 226 _(msg`Change app icon to "${icon.name}"`), 227 _(msg`The app will be restarted`), 228 [ 229 { 230 text: _(msg`Cancel`), 231 style: 'cancel', 232 }, 233 { 234 text: _(msg`OK`), 235 onPress: () => { 236 DynamicAppIcon.setAppIcon(icon.id) 237 }, 238 style: 'default', 239 }, 240 ], 241 ) 242 } else { 243 DynamicAppIcon.setAppIcon(icon.id) 244 } 245 }}> 246 <AppIconImage icon={icon} size={size} /> 247 </PressableScale> 248 ) 249}