Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
120
fork

Configure Feed

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

at a876aae44ea07494ebea9727350aa060b81f317b 237 lines 7.5 kB view raw
1import {View} from 'react-native' 2import { 3 type $Typed, 4 type AppBskyGraphDefs, 5 type AppBskyGraphListitem, 6 type AppBskyGraphStarterpack, 7 AtUri, 8 type ComAtprotoRepoApplyWrites, 9} from '@atproto/api' 10import {TID} from '@atproto/common-web' 11import {msg} from '@lingui/core/macro' 12import {useLingui} from '@lingui/react' 13import {Trans} from '@lingui/react/macro' 14import {useNavigation} from '@react-navigation/native' 15import {useQueryClient} from '@tanstack/react-query' 16import chunk from 'lodash.chunk' 17 18import {until} from '#/lib/async/until' 19import {wait} from '#/lib/async/wait' 20import {type NavigationProp} from '#/lib/routes/types' 21import {logger} from '#/logger' 22import {getAllListMembers} from '#/state/queries/list-members' 23import {useAgent, useSession} from '#/state/session' 24import {atoms as a, platform, useTheme, web} from '#/alf' 25import {Admonition} from '#/components/Admonition' 26import {Button, ButtonText} from '#/components/Button' 27import * as Dialog from '#/components/Dialog' 28import {Loader} from '#/components/Loader' 29import * as Toast from '#/components/Toast' 30import {Text} from '#/components/Typography' 31import {useAnalytics} from '#/analytics' 32import {CreateOrEditListDialog} from './CreateOrEditListDialog' 33 34export function CreateListFromStarterPackDialog({ 35 control, 36 starterPack, 37}: { 38 control: Dialog.DialogControlProps 39 starterPack: AppBskyGraphDefs.StarterPackView 40}) { 41 const {_} = useLingui() 42 const t = useTheme() 43 const agent = useAgent() 44 const ax = useAnalytics() 45 const {currentAccount} = useSession() 46 const navigation = useNavigation<NavigationProp>() 47 const queryClient = useQueryClient() 48 const createDialogControl = Dialog.useDialogControl() 49 const loadingDialogControl = Dialog.useDialogControl() 50 51 const record = starterPack.record as AppBskyGraphStarterpack.Record 52 53 const onPressCreate = () => { 54 control.close(() => createDialogControl.open()) 55 } 56 57 const addMembersAndNavigate = async (listUri: string) => { 58 const navigateToList = () => { 59 const urip = new AtUri(listUri) 60 navigation.navigate('ProfileList', { 61 name: urip.hostname, 62 rkey: urip.rkey, 63 }) 64 } 65 66 if (!starterPack.list || !currentAccount) { 67 loadingDialogControl.close(navigateToList) 68 return 69 } 70 71 try { 72 // Fetch all members and add them, with minimum 3s duration for UX 73 const listItems = await wait( 74 3000, 75 (async () => { 76 const items = await getAllListMembers(agent, starterPack.list!.uri) 77 78 if (items.length > 0) { 79 const listitemWrites: $Typed<ComAtprotoRepoApplyWrites.Create>[] = 80 items.map(item => { 81 const listitemRecord: $Typed<AppBskyGraphListitem.Record> = { 82 $type: 'app.bsky.graph.listitem', 83 subject: item.subject.did, 84 list: listUri, 85 createdAt: new Date().toISOString(), 86 } 87 return { 88 $type: 'com.atproto.repo.applyWrites#create', 89 collection: 'app.bsky.graph.listitem', 90 rkey: TID.nextStr(), 91 value: listitemRecord, 92 } 93 }) 94 95 const chunks = chunk(listitemWrites, 50) 96 for (const c of chunks) { 97 await agent.com.atproto.repo.applyWrites({ 98 repo: currentAccount.did, 99 writes: c, 100 }) 101 } 102 103 await until( 104 5, 105 1e3, 106 (res: {data: {items: unknown[]}}) => res.data.items.length > 0, 107 () => 108 agent.app.bsky.graph.getList({ 109 list: listUri, 110 limit: 1, 111 }), 112 ) 113 } 114 115 return items 116 })(), 117 ) 118 119 queryClient.invalidateQueries({queryKey: ['list-members', listUri]}) 120 121 ax.metric('starterPack:convertToList', { 122 starterPack: starterPack.uri, 123 memberCount: listItems.length, 124 }) 125 } catch (e) { 126 logger.error('Failed to add members to list', {safeMessage: e}) 127 Toast.show(_(msg`List created, but failed to add some members`), { 128 type: 'error', 129 }) 130 } 131 132 loadingDialogControl.close(navigateToList) 133 } 134 135 const onListCreated = (listUri: string) => { 136 loadingDialogControl.open() 137 addMembersAndNavigate(listUri) 138 } 139 140 return ( 141 <> 142 <Dialog.Outer 143 control={control} 144 testID="createListFromStarterPackDialog" 145 nativeOptions={{preventExpansion: true}}> 146 <Dialog.Handle /> 147 <Dialog.ScrollableInner 148 label={_(msg`Create list from starter pack`)} 149 style={web({maxWidth: 400})}> 150 <View style={[a.gap_lg]}> 151 <Text style={[a.text_xl, a.font_bold]}> 152 <Trans>Create list from starter pack</Trans> 153 </Text> 154 155 <Text 156 style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high]}> 157 <Trans> 158 This will create a new list with the same name, description, and 159 members as this starter pack. 160 </Trans> 161 </Text> 162 163 <Admonition type="tip"> 164 <Trans> 165 Changes to the starter pack will not be reflected in the list 166 after creation. The list will be an independent copy. 167 </Trans> 168 </Admonition> 169 170 <View 171 style={[ 172 platform({ 173 web: [a.flex_row_reverse], 174 native: [a.flex_col], 175 }), 176 a.gap_md, 177 a.pt_sm, 178 ]}> 179 <Button 180 label={_(msg`Create list`)} 181 onPress={onPressCreate} 182 size={platform({ 183 web: 'small', 184 native: 'large', 185 })} 186 color="primary"> 187 <ButtonText> 188 <Trans>Create list</Trans> 189 </ButtonText> 190 </Button> 191 <Button 192 label={_(msg`Cancel`)} 193 onPress={() => control.close()} 194 size={platform({ 195 web: 'small', 196 native: 'large', 197 })} 198 color="secondary"> 199 <ButtonText> 200 <Trans>Cancel</Trans> 201 </ButtonText> 202 </Button> 203 </View> 204 </View> 205 <Dialog.Close /> 206 </Dialog.ScrollableInner> 207 </Dialog.Outer> 208 209 <CreateOrEditListDialog 210 control={createDialogControl} 211 purpose="app.bsky.graph.defs#curatelist" 212 onSave={onListCreated} 213 initialValues={{ 214 name: record.name, 215 description: record.description, 216 avatar: starterPack.list?.avatar, 217 }} 218 /> 219 220 <Dialog.Outer 221 control={loadingDialogControl} 222 nativeOptions={{preventDismiss: true}}> 223 <Dialog.Handle /> 224 <Dialog.ScrollableInner 225 label={_(msg`Adding members to list...`)} 226 style={web({maxWidth: 400})}> 227 <View style={[a.align_center, a.gap_lg, a.py_5xl]}> 228 <Loader size="xl" /> 229 <Text style={[a.text_lg, t.atoms.text_contrast_high]}> 230 <Trans>Adding members to list...</Trans> 231 </Text> 232 </View> 233 </Dialog.ScrollableInner> 234 </Dialog.Outer> 235 </> 236 ) 237}