forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useCallback, useMemo, useState} from 'react'
2import {type StyleProp, View, type ViewStyle} from 'react-native'
3import {type AppBskyActorDefs as ActorDefs} from '@atproto/api'
4import {Trans} from '@lingui/macro'
5import {useFocusEffect} from '@react-navigation/native'
6import {type NativeStackScreenProps} from '@react-navigation/native-stack'
7
8import {type CommonNavigatorParams} from '#/lib/routes/types'
9import {cleanError} from '#/lib/strings/errors'
10import {logger} from '#/logger'
11import {useModerationOpts} from '#/state/preferences/moderation-opts'
12import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts'
13import {useSetMinimalShellMode} from '#/state/shell'
14import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
15import {List} from '#/view/com/util/List'
16import {atoms as a, useTheme} from '#/alf'
17import * as Layout from '#/components/Layout'
18import {ListFooter} from '#/components/Lists'
19import * as ProfileCard from '#/components/ProfileCard'
20import {Text} from '#/components/Typography'
21
22type Props = NativeStackScreenProps<
23 CommonNavigatorParams,
24 'ModerationBlockedAccounts'
25>
26export function ModerationBlockedAccounts({}: Props) {
27 const t = useTheme()
28 const setMinimalShellMode = useSetMinimalShellMode()
29 const moderationOpts = useModerationOpts()
30
31 const [isPTRing, setIsPTRing] = useState(false)
32 const {
33 data,
34 isFetching,
35 isError,
36 error,
37 refetch,
38 hasNextPage,
39 fetchNextPage,
40 isFetchingNextPage,
41 } = useMyBlockedAccountsQuery()
42 const isEmpty = !isFetching && !data?.pages[0]?.blocks.length
43 const profiles = useMemo(() => {
44 if (data?.pages) {
45 return data.pages.flatMap(page => page.blocks)
46 }
47 return []
48 }, [data])
49
50 useFocusEffect(
51 useCallback(() => {
52 setMinimalShellMode(false)
53 }, [setMinimalShellMode]),
54 )
55
56 const onRefresh = useCallback(async () => {
57 setIsPTRing(true)
58 try {
59 await refetch()
60 } catch (err) {
61 logger.error('Failed to refresh my muted accounts', {message: err})
62 }
63 setIsPTRing(false)
64 }, [refetch, setIsPTRing])
65
66 const onEndReached = useCallback(async () => {
67 if (isFetching || !hasNextPage || isError) return
68
69 try {
70 await fetchNextPage()
71 } catch (err) {
72 logger.error('Failed to load more of my muted accounts', {message: err})
73 }
74 }, [isFetching, hasNextPage, isError, fetchNextPage])
75
76 const renderItem = ({
77 item,
78 index,
79 }: {
80 item: ActorDefs.ProfileView
81 index: number
82 }) => {
83 if (!moderationOpts) return null
84 return (
85 <View
86 style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]}
87 key={item.did}>
88 <ProfileCard.Default
89 testID={`blockedAccount-${index}`}
90 profile={item}
91 moderationOpts={moderationOpts}
92 />
93 </View>
94 )
95 }
96
97 return (
98 <Layout.Screen testID="blockedAccountsScreen">
99 <Layout.Center>
100 <Layout.Header.Outer>
101 <Layout.Header.BackButton />
102 <Layout.Header.Content>
103 <Layout.Header.TitleText>
104 <Trans>Blocked Accounts</Trans>
105 </Layout.Header.TitleText>
106 </Layout.Header.Content>
107 <Layout.Header.Slot />
108 </Layout.Header.Outer>
109 {isEmpty ? (
110 <View>
111 <Info style={[a.border_b]} />
112 {isError ? (
113 <ErrorScreen
114 title="Oops!"
115 message={cleanError(error)}
116 onPressTryAgain={refetch}
117 />
118 ) : (
119 <Empty />
120 )}
121 </View>
122 ) : (
123 <List
124 data={profiles}
125 keyExtractor={(item: ActorDefs.ProfileView) => item.did}
126 refreshing={isPTRing}
127 onRefresh={onRefresh}
128 onEndReached={onEndReached}
129 renderItem={renderItem}
130 initialNumToRender={15}
131 // FIXME(dan)
132
133 ListHeaderComponent={Info}
134 ListFooterComponent={
135 <ListFooter
136 isFetchingNextPage={isFetchingNextPage}
137 hasNextPage={hasNextPage}
138 error={cleanError(error)}
139 onRetry={fetchNextPage}
140 />
141 }
142 />
143 )}
144 </Layout.Center>
145 </Layout.Screen>
146 )
147}
148
149function Empty() {
150 const t = useTheme()
151 return (
152 <View style={[a.pt_2xl, a.px_xl, a.align_center]}>
153 <View
154 style={[
155 a.py_md,
156 a.px_lg,
157 a.rounded_sm,
158 t.atoms.bg_contrast_25,
159 a.border,
160 t.atoms.border_contrast_low,
161 {maxWidth: 400},
162 ]}>
163 <Text style={[a.text_sm, a.text_center, t.atoms.text_contrast_high]}>
164 <Trans>
165 You have not blocked any accounts yet. To block an account, go to
166 their profile and select "Block account" from the menu on their
167 account.
168 </Trans>
169 </Text>
170 </View>
171 </View>
172 )
173}
174
175function Info({style}: {style?: StyleProp<ViewStyle>}) {
176 const t = useTheme()
177 return (
178 <View
179 style={[
180 a.w_full,
181 t.atoms.bg_contrast_25,
182 a.py_md,
183 a.px_xl,
184 a.border_t,
185 {marginTop: a.border.borderWidth * -1},
186 t.atoms.border_contrast_low,
187 style,
188 ]}>
189 <Text style={[a.text_center, a.text_sm, t.atoms.text_contrast_high]}>
190 <Trans>
191 Blocked accounts cannot reply in your threads, mention you, or
192 otherwise interact with you. You will not see their content and they
193 will be prevented from seeing yours.
194 </Trans>
195 </Text>
196 </View>
197 )
198}