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 217 lines 7.4 kB view raw
1import {useCallback} from 'react' 2import {View} from 'react-native' 3import Animated, { 4 FadeIn, 5 FadeOut, 6 LayoutAnimationConfig, 7 LinearTransition, 8} from 'react-native-reanimated' 9import {type ComAtprotoServerListAppPasswords} from '@atproto/api' 10import {msg, Trans} from '@lingui/macro' 11import {useLingui} from '@lingui/react' 12import {type NativeStackScreenProps} from '@react-navigation/native-stack' 13 14import {type CommonNavigatorParams} from '#/lib/routes/types' 15import {cleanError} from '#/lib/strings/errors' 16import { 17 useAppPasswordDeleteMutation, 18 useAppPasswordsQuery, 19} from '#/state/queries/app-passwords' 20import {EmptyState} from '#/view/com/util/EmptyState' 21import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' 22import * as Toast from '#/view/com/util/Toast' 23import {atoms as a, useTheme} from '#/alf' 24import {Admonition, colors} from '#/components/Admonition' 25import {Button, ButtonIcon, ButtonText} from '#/components/Button' 26import {useDialogControl} from '#/components/Dialog' 27import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 28import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 29import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 30import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 31import * as Layout from '#/components/Layout' 32import {Loader} from '#/components/Loader' 33import * as Prompt from '#/components/Prompt' 34import {Text} from '#/components/Typography' 35import {AddAppPasswordDialog} from './components/AddAppPasswordDialog' 36import * as SettingsList from './components/SettingsList' 37 38type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'> 39export function AppPasswordsScreen({}: Props) { 40 const {_} = useLingui() 41 const {data: appPasswords, error} = useAppPasswordsQuery() 42 const createAppPasswordControl = useDialogControl() 43 44 return ( 45 <Layout.Screen testID="AppPasswordsScreen"> 46 <Layout.Header.Outer> 47 <Layout.Header.BackButton /> 48 <Layout.Header.Content> 49 <Layout.Header.TitleText> 50 <Trans>App Passwords</Trans> 51 </Layout.Header.TitleText> 52 </Layout.Header.Content> 53 <Layout.Header.Slot /> 54 </Layout.Header.Outer> 55 <Layout.Content> 56 {error ? ( 57 <ErrorScreen 58 title={_(msg`Oops!`)} 59 message={_(msg`There was an issue fetching your app passwords`)} 60 details={cleanError(error)} 61 /> 62 ) : ( 63 <SettingsList.Container> 64 <SettingsList.Item> 65 <Admonition type="tip" style={[a.flex_1]}> 66 <Trans> 67 Use app passwords to sign in to other Bluesky clients without 68 giving full access to your account or password. 69 </Trans> 70 </Admonition> 71 </SettingsList.Item> 72 <SettingsList.Item> 73 <Button 74 label={_(msg`Add App Password`)} 75 size="large" 76 color="primary" 77 variant="solid" 78 onPress={() => createAppPasswordControl.open()} 79 style={[a.flex_1]}> 80 <ButtonIcon icon={PlusIcon} /> 81 <ButtonText> 82 <Trans>Add App Password</Trans> 83 </ButtonText> 84 </Button> 85 </SettingsList.Item> 86 <SettingsList.Divider /> 87 <LayoutAnimationConfig skipEntering skipExiting> 88 {appPasswords ? ( 89 appPasswords.length > 0 ? ( 90 <View style={[a.overflow_hidden]}> 91 {appPasswords.map(appPassword => ( 92 <Animated.View 93 key={appPassword.name} 94 style={a.w_full} 95 entering={FadeIn} 96 exiting={FadeOut} 97 layout={LinearTransition.delay(150)}> 98 <SettingsList.Item> 99 <AppPasswordCard appPassword={appPassword} /> 100 </SettingsList.Item> 101 </Animated.View> 102 ))} 103 </View> 104 ) : ( 105 <EmptyState 106 icon={Growth} 107 message={_(msg`No app passwords yet`)} 108 /> 109 ) 110 ) : ( 111 <View 112 style={[ 113 a.flex_1, 114 a.justify_center, 115 a.align_center, 116 a.py_4xl, 117 ]}> 118 <Loader size="xl" /> 119 </View> 120 )} 121 </LayoutAnimationConfig> 122 </SettingsList.Container> 123 )} 124 </Layout.Content> 125 126 <AddAppPasswordDialog 127 control={createAppPasswordControl} 128 passwords={appPasswords?.map(p => p.name) || []} 129 /> 130 </Layout.Screen> 131 ) 132} 133 134function AppPasswordCard({ 135 appPassword, 136}: { 137 appPassword: ComAtprotoServerListAppPasswords.AppPassword 138}) { 139 const t = useTheme() 140 const {i18n, _} = useLingui() 141 const deleteControl = Prompt.usePromptControl() 142 const {mutateAsync: deleteMutation} = useAppPasswordDeleteMutation() 143 144 const onDelete = useCallback(async () => { 145 await deleteMutation({name: appPassword.name}) 146 Toast.show(_(msg({message: 'App password deleted', context: 'toast'}))) 147 }, [deleteMutation, appPassword.name, _]) 148 149 return ( 150 <View 151 style={[ 152 a.w_full, 153 a.border, 154 a.rounded_sm, 155 a.px_md, 156 a.py_sm, 157 t.atoms.bg_contrast_25, 158 t.atoms.border_contrast_low, 159 ]}> 160 <View 161 style={[ 162 a.flex_row, 163 a.justify_between, 164 a.align_start, 165 a.w_full, 166 a.gap_sm, 167 ]}> 168 <View style={[a.gap_xs]}> 169 <Text style={[t.atoms.text, a.text_md, a.font_semi_bold]}> 170 {appPassword.name} 171 </Text> 172 <Text style={[t.atoms.text_contrast_medium]}> 173 <Trans> 174 Created{' '} 175 {i18n.date(appPassword.createdAt, { 176 year: 'numeric', 177 month: 'numeric', 178 day: 'numeric', 179 hour: '2-digit', 180 minute: '2-digit', 181 })} 182 </Trans> 183 </Text> 184 </View> 185 <Button 186 label={_(msg`Delete app password`)} 187 variant="ghost" 188 color="negative" 189 size="small" 190 shape="square" 191 style={[a.bg_transparent]} 192 onPress={() => deleteControl.open()}> 193 <ButtonIcon icon={TrashIcon} /> 194 </Button> 195 </View> 196 {appPassword.privileged && ( 197 <View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}> 198 <WarningIcon style={[{color: colors.warning}]} /> 199 <Text style={t.atoms.text_contrast_high}> 200 <Trans>Allows access to direct messages</Trans> 201 </Text> 202 </View> 203 )} 204 205 <Prompt.Basic 206 control={deleteControl} 207 title={_(msg`Delete app password?`)} 208 description={_( 209 msg`Are you sure you want to delete the app password "${appPassword.name}"?`, 210 )} 211 onConfirm={onDelete} 212 confirmButtonCta={_(msg`Delete`)} 213 confirmButtonColor="negative" 214 /> 215 </View> 216 ) 217}