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