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