diff options
Diffstat (limited to 'src/view/screens/ModerationMutedAccounts.tsx')
-rw-r--r-- | src/view/screens/ModerationMutedAccounts.tsx | 231 |
1 files changed, 133 insertions, 98 deletions
diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 2fa27ee54..41aee9f2f 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react' +import React from 'react' import { ActivityIndicator, FlatList, @@ -8,129 +8,164 @@ import { } from 'react-native' import {AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Text} from '../com/util/text/Text' -import {useStores} from 'state/index' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' -import {observer} from 'mobx-react-lite' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {CommonNavigatorParams} from 'lib/routes/types' -import {MutedAccountsModel} from 'state/models/lists/muted-accounts' import {useAnalytics} from 'lib/analytics/analytics' import {useFocusEffect} from '@react-navigation/native' import {ViewHeader} from '../com/util/ViewHeader' import {CenteredView} from 'view/com/util/Views' +import {ErrorScreen} from '../com/util/error/ErrorScreen' import {ProfileCard} from 'view/com/profile/ProfileCard' import {logger} from '#/logger' import {useSetMinimalShellMode} from '#/state/shell' +import {Trans, msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts' +import {cleanError} from '#/lib/strings/errors' type Props = NativeStackScreenProps< CommonNavigatorParams, 'ModerationMutedAccounts' > -export const ModerationMutedAccounts = withAuthRequired( - observer(function ModerationMutedAccountsImpl({}: Props) { - const pal = usePalette('default') - const store = useStores() - const setMinimalShellMode = useSetMinimalShellMode() - const {isTabletOrDesktop} = useWebMediaQueries() - const {screen} = useAnalytics() - const mutedAccounts = useMemo(() => new MutedAccountsModel(store), [store]) +export function ModerationMutedAccounts({}: Props) { + const pal = usePalette('default') + const {_} = useLingui() + const setMinimalShellMode = useSetMinimalShellMode() + const {isTabletOrDesktop} = useWebMediaQueries() + const {screen} = useAnalytics() + const [isPTRing, setIsPTRing] = React.useState(false) + const { + data, + isFetching, + isError, + error, + refetch, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + } = useMyMutedAccountsQuery() + const isEmpty = !isFetching && !data?.pages[0]?.mutes.length + const profiles = React.useMemo(() => { + if (data?.pages) { + return data.pages.flatMap(page => page.mutes) + } + return [] + }, [data]) - useFocusEffect( - React.useCallback(() => { - screen('MutedAccounts') - setMinimalShellMode(false) - mutedAccounts.refresh() - }, [screen, setMinimalShellMode, mutedAccounts]), - ) + useFocusEffect( + React.useCallback(() => { + screen('MutedAccounts') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) - const onRefresh = React.useCallback(() => { - mutedAccounts.refresh() - }, [mutedAccounts]) - const onEndReached = React.useCallback(() => { - mutedAccounts - .loadMore() - .catch(err => - logger.error('Failed to load more muted accounts', {error: err}), - ) - }, [mutedAccounts]) + const onRefresh = React.useCallback(async () => { + setIsPTRing(true) + try { + await refetch() + } catch (err) { + logger.error('Failed to refresh my muted accounts', {error: err}) + } + setIsPTRing(false) + }, [refetch, setIsPTRing]) - const renderItem = ({ - item, - index, - }: { - item: ActorDefs.ProfileView - index: number - }) => ( - <ProfileCard - testID={`mutedAccount-${index}`} - key={item.did} - profile={item} - /> - ) - return ( - <CenteredView + const onEndReached = React.useCallback(async () => { + if (isFetching || !hasNextPage || isError) return + + try { + await fetchNextPage() + } catch (err) { + logger.error('Failed to load more of my muted accounts', {error: err}) + } + }, [isFetching, hasNextPage, isError, fetchNextPage]) + + const renderItem = ({ + item, + index, + }: { + item: ActorDefs.ProfileView + index: number + }) => ( + <ProfileCard + testID={`mutedAccount-${index}`} + key={item.did} + profile={item} + /> + ) + return ( + <CenteredView + style={[ + styles.container, + isTabletOrDesktop && styles.containerDesktop, + pal.view, + pal.border, + ]} + testID="mutedAccountsScreen"> + <ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop /> + <Text + type="sm" style={[ - styles.container, - isTabletOrDesktop && styles.containerDesktop, - pal.view, - pal.border, - ]} - testID="mutedAccountsScreen"> - <ViewHeader title="Muted Accounts" showOnDesktop /> - <Text - type="sm" - style={[ - styles.description, - pal.text, - isTabletOrDesktop && styles.descriptionDesktop, - ]}> + styles.description, + pal.text, + isTabletOrDesktop && styles.descriptionDesktop, + ]}> + <Trans> Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private. - </Text> - {!mutedAccounts.hasContent ? ( - <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}> + </Trans> + </Text> + {isEmpty ? ( + <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}> + {isError ? ( + <ErrorScreen + title="Oops!" + message={cleanError(error)} + onPressTryAgain={refetch} + /> + ) : ( <View style={[styles.empty, pal.viewLight]}> <Text type="lg" style={[pal.text, styles.emptyText]}> - You have not muted any accounts yet. To mute an account, go to - their profile and selected "Mute account" from the menu on their - account. + <Trans> + You have not muted any accounts yet. To mute an account, go to + their profile and selected "Mute account" from the menu on + their account. + </Trans> </Text> </View> - </View> - ) : ( - <FlatList - style={[!isTabletOrDesktop && styles.flex1]} - data={mutedAccounts.mutes} - keyExtractor={item => item.did} - refreshControl={ - <RefreshControl - refreshing={mutedAccounts.isRefreshing} - onRefresh={onRefresh} - tintColor={pal.colors.text} - titleColor={pal.colors.text} - /> - } - onEndReached={onEndReached} - renderItem={renderItem} - initialNumToRender={15} - // FIXME(dan) - // eslint-disable-next-line react/no-unstable-nested-components - ListFooterComponent={() => ( - <View style={styles.footer}> - {mutedAccounts.isLoading && <ActivityIndicator />} - </View> - )} - extraData={mutedAccounts.isLoading} - // @ts-ignore our .web version only -prf - desktopFixedHeight - /> - )} - </CenteredView> - ) - }), -) + )} + </View> + ) : ( + <FlatList + style={[!isTabletOrDesktop && styles.flex1]} + data={profiles} + keyExtractor={item => item.did} + refreshControl={ + <RefreshControl + refreshing={isPTRing} + onRefresh={onRefresh} + tintColor={pal.colors.text} + titleColor={pal.colors.text} + /> + } + onEndReached={onEndReached} + renderItem={renderItem} + initialNumToRender={15} + // FIXME(dan) + + ListFooterComponent={() => ( + <View style={styles.footer}> + {(isFetching || isFetchingNextPage) && <ActivityIndicator />} + </View> + )} + // @ts-ignore our .web version only -prf + desktopFixedHeight + /> + )} + </CenteredView> + ) +} const styles = StyleSheet.create({ container: { |