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

Configure Feed

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

at 6bfe758d2a9ea376552fb45e5e589bccd0cf4df5 192 lines 5.5 kB view raw
1import {useCallback, useEffect, useMemo} from 'react' 2import {View} from 'react-native' 3import {msg, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5 6import {useCallOnce} from '#/lib/once' 7import {EmptyState} from '#/view/com/util/EmptyState' 8import {atoms as a, select, useBreakpoints, useTheme, web} from '#/alf' 9import {Button, ButtonText} from '#/components/Button' 10import * as Dialog from '#/components/Dialog' 11import {PageX_Stroke2_Corner0_Rounded_Large as PageXIcon} from '#/components/icons/PageX' 12import {ListFooter} from '#/components/Lists' 13import {Loader} from '#/components/Loader' 14import {Text} from '#/components/Typography' 15import {useAnalytics} from '#/analytics' 16import {IS_NATIVE} from '#/env' 17import {DraftItem} from './DraftItem' 18import {useDeleteDraftMutation, useDraftsQuery} from './state/queries' 19import {type DraftSummary} from './state/schema' 20 21export function DraftsListDialog({ 22 control, 23 onSelectDraft, 24}: { 25 control: Dialog.DialogControlProps 26 onSelectDraft: (draft: DraftSummary) => void 27}) { 28 const {_} = useLingui() 29 const t = useTheme() 30 const {gtPhone} = useBreakpoints() 31 const ax = useAnalytics() 32 const {data, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage} = 33 useDraftsQuery() 34 const {mutate: deleteDraft} = useDeleteDraftMutation() 35 36 const drafts = useMemo( 37 () => data?.pages.flatMap(page => page.drafts) ?? [], 38 [data], 39 ) 40 41 // Fire draft:listOpen metric when dialog opens and data is loaded 42 const draftCount = drafts.length 43 const isDataReady = !isLoading && data !== undefined 44 const onDraftListOpen = useCallOnce() 45 useEffect(() => { 46 if (isDataReady) { 47 onDraftListOpen(() => { 48 ax.metric('draft:listOpen', { 49 draftCount, 50 }) 51 }) 52 } 53 }, [onDraftListOpen, isDataReady, draftCount, ax]) 54 55 const handleSelectDraft = useCallback( 56 (summary: DraftSummary) => { 57 control.close(() => { 58 onSelectDraft(summary) 59 }) 60 }, 61 [control, onSelectDraft], 62 ) 63 64 const handleDeleteDraft = useCallback( 65 (draftSummary: DraftSummary) => { 66 // Fire draft:delete metric 67 const draftAgeMs = Date.now() - new Date(draftSummary.createdAt).getTime() 68 ax.metric('draft:delete', { 69 logContext: 'DraftsList', 70 draftAgeMs, 71 }) 72 deleteDraft({draftId: draftSummary.id, draft: draftSummary.draft}) 73 }, 74 [deleteDraft, ax], 75 ) 76 77 const backButton = useCallback( 78 () => ( 79 <Button 80 label={_(msg`Back`)} 81 onPress={() => control.close()} 82 size="small" 83 color="primary" 84 variant="ghost"> 85 <ButtonText style={[a.text_md]}> 86 <Trans>Back</Trans> 87 </ButtonText> 88 </Button> 89 ), 90 [control, _], 91 ) 92 93 const renderItem = useCallback( 94 ({item}: {item: DraftSummary}) => { 95 return ( 96 <View style={[gtPhone ? [a.px_md, a.pt_md] : [a.px_sm, a.pt_sm]]}> 97 <DraftItem 98 draft={item} 99 onSelect={handleSelectDraft} 100 onDelete={handleDeleteDraft} 101 /> 102 </View> 103 ) 104 }, 105 [handleSelectDraft, handleDeleteDraft, gtPhone], 106 ) 107 108 const header = useMemo( 109 () => ( 110 <Dialog.Header renderLeft={backButton}> 111 <Dialog.HeaderText> 112 <Trans>Drafts</Trans> 113 </Dialog.HeaderText> 114 </Dialog.Header> 115 ), 116 [backButton], 117 ) 118 119 const onEndReached = useCallback(() => { 120 if (hasNextPage && !isFetchingNextPage) { 121 void fetchNextPage() 122 } 123 }, [hasNextPage, isFetchingNextPage, fetchNextPage]) 124 125 const emptyComponent = useMemo(() => { 126 if (isLoading) { 127 return ( 128 <View style={[a.py_xl, a.align_center]}> 129 <Loader size="lg" /> 130 </View> 131 ) 132 } 133 return ( 134 <EmptyState 135 icon={PageXIcon} 136 message={_(msg`No drafts yet`)} 137 style={[a.justify_center, {minHeight: 500}]} 138 /> 139 ) 140 }, [isLoading, _]) 141 142 const footerComponent = useMemo( 143 () => ( 144 <> 145 {drafts.length > 5 && ( 146 <View style={[a.align_center, a.py_2xl]}> 147 <Text style={[a.text_center, t.atoms.text_contrast_medium]}> 148 <Trans>So many thoughts, you should post one</Trans> 149 </Text> 150 </View> 151 )} 152 <ListFooter 153 isFetchingNextPage={isFetchingNextPage} 154 hasNextPage={hasNextPage} 155 style={[a.border_transparent]} 156 /> 157 </> 158 ), 159 [isFetchingNextPage, hasNextPage, drafts.length, t], 160 ) 161 162 return ( 163 <Dialog.Outer control={control}> 164 {/* We really really need to figure out a nice, consistent API for doing a header cross-platform -sfn */} 165 {IS_NATIVE && header} 166 <Dialog.InnerFlatList 167 data={drafts} 168 renderItem={renderItem} 169 keyExtractor={(item: DraftSummary) => item.id} 170 ListHeaderComponent={web(header)} 171 stickyHeaderIndices={web([0])} 172 ListEmptyComponent={emptyComponent} 173 ListFooterComponent={footerComponent} 174 onEndReached={onEndReached} 175 onEndReachedThreshold={0.5} 176 style={[ 177 a.px_0, 178 web({minHeight: 500}), 179 { 180 backgroundColor: select(t.name, { 181 light: t.palette.contrast_50, 182 dark: t.palette.contrast_0, 183 dim: '#000000', 184 }), 185 }, 186 ]} 187 webInnerContentContainerStyle={[a.py_0]} 188 contentContainerStyle={[a.pb_xl]} 189 /> 190 </Dialog.Outer> 191 ) 192}