diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/lists/ListMembers.tsx | 3 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 19 | ||||
-rw-r--r-- | src/view/screens/ModerationBlockedAccounts.tsx | 102 | ||||
-rw-r--r-- | src/view/screens/ModerationMutedAccounts.tsx | 99 |
4 files changed, 148 insertions, 75 deletions
diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index 4a25c53e6..940761e31 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -64,6 +64,7 @@ export function ListMembers({ const { data, + dataUpdatedAt, isFetching, isFetched, isError, @@ -184,6 +185,7 @@ export function ListMembers({ (item as AppBskyGraphDefs.ListItemView).subject.handle }`} profile={(item as AppBskyGraphDefs.ListItemView).subject} + dataUpdatedAt={dataUpdatedAt} renderButton={renderMemberButton} style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}} /> @@ -196,6 +198,7 @@ export function ListMembers({ onPressTryAgain, onPressRetryLoadMore, isMobile, + dataUpdatedAt, ], ) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index f7340fd6f..95f0ecd93 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -21,10 +21,13 @@ import { getProfileModerationCauses, getModerationCauseKey, } from 'lib/moderation' +import {useModerationOpts} from '#/state/queries/preferences' +import {useProfileShadow} from '#/state/cache/profile-shadow' -export const ProfileCard = observer(function ProfileCardImpl({ +export function ProfileCard({ testID, - profile, + profile: profileUnshadowed, + dataUpdatedAt, noBg, noBorder, followers, @@ -33,16 +36,20 @@ export const ProfileCard = observer(function ProfileCardImpl({ }: { testID?: string profile: AppBskyActorDefs.ProfileViewBasic + dataUpdatedAt: number noBg?: boolean noBorder?: boolean followers?: AppBskyActorDefs.ProfileView[] | undefined renderButton?: (profile: AppBskyActorDefs.ProfileViewBasic) => React.ReactNode style?: StyleProp<ViewStyle> }) { - const store = useStores() const pal = usePalette('default') - - const moderation = moderateProfile(profile, store.preferences.moderationOpts) + const profile = useProfileShadow(profileUnshadowed, dataUpdatedAt) + const moderationOpts = useModerationOpts() + if (!moderationOpts) { + return null + } + const moderation = moderateProfile(profile, moderationOpts) return ( <Link @@ -100,7 +107,7 @@ export const ProfileCard = observer(function ProfileCardImpl({ <FollowersList followers={followers} /> </Link> ) -}) +} function ProfileCardPills({ followedBy, diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index 0dc3b706b..702a8d44e 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react' +import React from 'react' import { ActivityIndicator, FlatList, @@ -8,56 +8,78 @@ 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 {BlockedAccountsModel} from 'state/models/lists/blocked-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 {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts' +import {cleanError} from '#/lib/strings/errors' type Props = NativeStackScreenProps< CommonNavigatorParams, 'ModerationBlockedAccounts' > export const ModerationBlockedAccounts = withAuthRequired( - observer(function ModerationBlockedAccountsImpl({}: Props) { + function ModerationBlockedAccountsImpl({}: Props) { const pal = usePalette('default') - const store = useStores() const setMinimalShellMode = useSetMinimalShellMode() const {isTabletOrDesktop} = useWebMediaQueries() const {screen} = useAnalytics() - const blockedAccounts = useMemo( - () => new BlockedAccountsModel(store), - [store], - ) + const [isPTRing, setIsPTRing] = React.useState(false) + const { + data, + dataUpdatedAt, + isFetching, + isError, + error, + refetch, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + } = useMyBlockedAccountsQuery() + const isEmpty = !isFetching && !data?.pages[0]?.blocks.length + const profiles = React.useMemo(() => { + if (data?.pages) { + return data.pages.flatMap(page => page.blocks) + } + return [] + }, [data]) useFocusEffect( React.useCallback(() => { screen('BlockedAccounts') setMinimalShellMode(false) - blockedAccounts.refresh() - }, [screen, setMinimalShellMode, blockedAccounts]), + }, [screen, setMinimalShellMode]), ) - const onRefresh = React.useCallback(() => { - blockedAccounts.refresh() - }, [blockedAccounts]) - const onEndReached = React.useCallback(() => { - blockedAccounts - .loadMore() - .catch(err => - logger.error('Failed to load more blocked accounts', {error: err}), - ) - }, [blockedAccounts]) + 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 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, @@ -70,6 +92,7 @@ export const ModerationBlockedAccounts = withAuthRequired( testID={`blockedAccount-${index}`} key={item.did} profile={item} + dataUpdatedAt={dataUpdatedAt} /> ) return ( @@ -93,24 +116,32 @@ export const ModerationBlockedAccounts = withAuthRequired( otherwise interact with you. You will not see their content and they will be prevented from seeing yours. </Text> - {!blockedAccounts.hasContent ? ( + {isEmpty ? ( <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}> - <View style={[styles.empty, pal.viewLight]}> - <Text type="lg" style={[pal.text, styles.emptyText]}> - You have not blocked any accounts yet. To block an account, go - to their profile and selected "Block account" from the menu on - their account. - </Text> - </View> + {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 blocked any accounts yet. To block an account, go + to their profile and selected "Block account" from the menu on + their account. + </Text> + </View> + )} </View> ) : ( <FlatList style={[!isTabletOrDesktop && styles.flex1]} - data={blockedAccounts.blocks} + data={profiles} keyExtractor={(item: ActorDefs.ProfileView) => item.did} refreshControl={ <RefreshControl - refreshing={blockedAccounts.isRefreshing} + refreshing={isPTRing} onRefresh={onRefresh} tintColor={pal.colors.text} titleColor={pal.colors.text} @@ -120,20 +151,19 @@ export const ModerationBlockedAccounts = withAuthRequired( renderItem={renderItem} initialNumToRender={15} // FIXME(dan) - // eslint-disable-next-line react/no-unstable-nested-components + ListFooterComponent={() => ( <View style={styles.footer}> - {blockedAccounts.isLoading && <ActivityIndicator />} + {(isFetching || isFetchingNextPage) && <ActivityIndicator />} </View> )} - extraData={blockedAccounts.isLoading} // @ts-ignore our .web version only -prf desktopFixedHeight /> )} </CenteredView> ) - }), + }, ) const styles = StyleSheet.create({ diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 2fa27ee54..fe0b4bf14 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,53 +8,78 @@ 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 {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) { + 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]) + const [isPTRing, setIsPTRing] = React.useState(false) + const { + data, + dataUpdatedAt, + 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]), + }, [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 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, @@ -67,6 +92,7 @@ export const ModerationMutedAccounts = withAuthRequired( testID={`mutedAccount-${index}`} key={item.did} profile={item} + dataUpdatedAt={dataUpdatedAt} /> ) return ( @@ -89,24 +115,32 @@ export const ModerationMutedAccounts = withAuthRequired( Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private. </Text> - {!mutedAccounts.hasContent ? ( + {isEmpty ? ( <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}> - <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. - </Text> - </View> + {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. + </Text> + </View> + )} </View> ) : ( <FlatList style={[!isTabletOrDesktop && styles.flex1]} - data={mutedAccounts.mutes} + data={profiles} keyExtractor={item => item.did} refreshControl={ <RefreshControl - refreshing={mutedAccounts.isRefreshing} + refreshing={isPTRing} onRefresh={onRefresh} tintColor={pal.colors.text} titleColor={pal.colors.text} @@ -116,20 +150,19 @@ export const ModerationMutedAccounts = withAuthRequired( renderItem={renderItem} initialNumToRender={15} // FIXME(dan) - // eslint-disable-next-line react/no-unstable-nested-components + ListFooterComponent={() => ( <View style={styles.footer}> - {mutedAccounts.isLoading && <ActivityIndicator />} + {(isFetching || isFetchingNextPage) && <ActivityIndicator />} </View> )} - extraData={mutedAccounts.isLoading} // @ts-ignore our .web version only -prf desktopFixedHeight /> )} </CenteredView> ) - }), + }, ) const styles = StyleSheet.create({ |