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

Configure Feed

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

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 {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' 11import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' 12import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' 13import {atoms as a, useTheme} from '#/alf' 14import * as Toggle from '#/components/forms/Toggle' 15import * as Layout from '#/components/Layout' 16import {Text} from '#/components/Typography' 17import {IS_ANDROID, IS_INTERNAL} from '#/env' 18 19type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'> 20export function AppIconSettingsScreen({}: Props) { 21 const t = useTheme() 22 const {_} = useLingui() 23 const sets = useAppIconSets() 24 const [currentAppIcon, setCurrentAppIcon] = useState(() => 25 getAppIconName(DynamicAppIcon.getAppIcon()), 26 ) 27 28 const onSetAppIcon = (icon: DynamicAppIcon.IconName) => { 29 if (IS_ANDROID) { 30 const next = 31 sets.defaults.find(i => i.id === icon) ?? 32 sets.core.find(i => i.id === icon) 33 Alert.alert( 34 next 35 ? _(msg`Change app icon to "${next.name}"`) 36 : _(msg`Change app icon`), 37 // unfortunately necessary -sfn 38 _(msg`The app will be restarted`), 39 [ 40 { 41 text: _(msg`Cancel`), 42 style: 'cancel', 43 }, 44 { 45 text: _(msg`OK`), 46 onPress: () => { 47 setCurrentAppIcon(setAppIcon(icon)) 48 }, 49 style: 'default', 50 }, 51 ], 52 ) 53 } else { 54 setCurrentAppIcon(setAppIcon(icon)) 55 } 56 } 57 58 return ( 59 <Layout.Screen> 60 <Layout.Header.Outer> 61 <Layout.Header.BackButton /> 62 <Layout.Header.Content> 63 <Layout.Header.TitleText> 64 <Trans>App Icon</Trans> 65 </Layout.Header.TitleText> 66 </Layout.Header.Content> 67 <Layout.Header.Slot /> 68 </Layout.Header.Outer> 69 70 <Layout.Content contentContainerStyle={[a.p_lg]}> 71 <Group 72 label={_(msg`Default icons`)} 73 value={currentAppIcon} 74 onChange={onSetAppIcon}> 75 {sets.defaults.map((icon, i) => ( 76 <Row 77 key={icon.id} 78 icon={icon} 79 isEnd={i === sets.defaults.length - 1}> 80 <AppIcon icon={icon} key={icon.id} size={40} /> 81 <RowText>{icon.name}</RowText> 82 </Row> 83 ))} 84 </Group> 85 86 {IS_INTERNAL && ( 87 <> 88 <Text 89 style={[ 90 a.text_md, 91 a.mt_xl, 92 a.mb_sm, 93 a.font_semi_bold, 94 t.atoms.text_contrast_medium, 95 ]}> 96 <Trans>Bluesky+</Trans> 97 </Text> 98 <Group 99 label={_(msg`Bluesky+ icons`)} 100 value={currentAppIcon} 101 onChange={onSetAppIcon}> 102 {sets.core.map((icon, i) => ( 103 <Row 104 key={icon.id} 105 icon={icon} 106 isEnd={i === sets.core.length - 1}> 107 <AppIcon icon={icon} key={icon.id} size={40} /> 108 <RowText>{icon.name}</RowText> 109 </Row> 110 ))} 111 </Group> 112 </> 113 )} 114 </Layout.Content> 115 </Layout.Screen> 116 ) 117} 118 119function setAppIcon(icon: DynamicAppIcon.IconName) { 120 if (icon === 'default_light') { 121 return getAppIconName(DynamicAppIcon.setAppIcon(null)) 122 } else { 123 return getAppIconName(DynamicAppIcon.setAppIcon(icon)) 124 } 125} 126 127function getAppIconName(icon: string | false): DynamicAppIcon.IconName { 128 if (!icon || icon === 'DEFAULT') { 129 return 'default_light' 130 } else { 131 return icon as DynamicAppIcon.IconName 132 } 133} 134 135function Group({ 136 children, 137 label, 138 value, 139 onChange, 140}: { 141 children: React.ReactNode 142 label: string 143 value: DynamicAppIcon.IconName 144 onChange: (value: DynamicAppIcon.IconName) => void 145}) { 146 return ( 147 <Toggle.Group 148 type="radio" 149 label={label} 150 values={[value]} 151 maxSelections={1} 152 onChange={vals => { 153 if (vals[0]) onChange(vals[0] as DynamicAppIcon.IconName) 154 }}> 155 <View style={[a.flex_1, a.rounded_md, a.overflow_hidden]}> 156 {children} 157 </View> 158 </Toggle.Group> 159 ) 160} 161 162function Row({ 163 icon, 164 children, 165 isEnd, 166}: { 167 icon: AppIconSet 168 children: React.ReactNode 169 isEnd: boolean 170}) { 171 const t = useTheme() 172 const {_} = useLingui() 173 174 return ( 175 <Toggle.Item label={_(msg`Set app icon to ${icon.name}`)} name={icon.id}> 176 {({hovered, pressed}) => ( 177 <View 178 style={[ 179 a.flex_1, 180 a.p_md, 181 a.flex_row, 182 a.gap_md, 183 a.align_center, 184 t.atoms.bg_contrast_25, 185 (hovered || pressed) && t.atoms.bg_contrast_50, 186 t.atoms.border_contrast_high, 187 !isEnd && a.border_b, 188 ]}> 189 {children} 190 <Toggle.Radio /> 191 </View> 192 )} 193 </Toggle.Item> 194 ) 195} 196 197function RowText({children}: {children: React.ReactNode}) { 198 const t = useTheme() 199 return ( 200 <Text 201 style={[ 202 a.text_md, 203 a.font_semi_bold, 204 a.flex_1, 205 t.atoms.text_contrast_medium, 206 ]} 207 emoji> 208 {children} 209 </Text> 210 ) 211} 212 213function AppIcon({icon, size = 50}: {icon: AppIconSet; size: number}) { 214 const {_} = useLingui() 215 return ( 216 <PressableScale 217 accessibilityLabel={icon.name} 218 accessibilityHint={_(msg`Changes app icon`)} 219 targetScale={0.95} 220 onPress={() => { 221 if (IS_ANDROID) { 222 Alert.alert( 223 _(msg`Change app icon to "${icon.name}"`), 224 _(msg`The app will be restarted`), 225 [ 226 { 227 text: _(msg`Cancel`), 228 style: 'cancel', 229 }, 230 { 231 text: _(msg`OK`), 232 onPress: () => { 233 DynamicAppIcon.setAppIcon(icon.id) 234 }, 235 style: 'default', 236 }, 237 ], 238 ) 239 } else { 240 DynamicAppIcon.setAppIcon(icon.id) 241 } 242 }}> 243 <AppIconImage icon={icon} size={size} /> 244 </PressableScale> 245 ) 246}