diff options
-rw-r--r-- | src/components/LikesDialog.tsx | 129 | ||||
-rw-r--r-- | src/components/ProfileCard.tsx | 53 | ||||
-rw-r--r-- | src/screens/Search/SearchResults.tsx | 4 | ||||
-rw-r--r-- | src/view/com/lists/ListMembers.tsx | 85 | ||||
-rw-r--r-- | src/view/com/profile/ProfileCard.tsx | 311 | ||||
-rw-r--r-- | src/view/screens/DebugMod.tsx | 32 | ||||
-rw-r--r-- | src/view/screens/ModerationBlockedAccounts.tsx | 35 | ||||
-rw-r--r-- | src/view/screens/ModerationMutedAccounts.tsx | 49 |
8 files changed, 189 insertions, 509 deletions
diff --git a/src/components/LikesDialog.tsx b/src/components/LikesDialog.tsx deleted file mode 100644 index cb000b433..000000000 --- a/src/components/LikesDialog.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import {useCallback, useMemo} from 'react' -import {ActivityIndicator, FlatList, View} from 'react-native' -import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {cleanError} from '#/lib/strings/errors' -import {logger} from '#/logger' -import {useLikedByQuery} from '#/state/queries/post-liked-by' -import {useResolveUriQuery} from '#/state/queries/resolve-uri' -import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' -import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' -import {atoms as a, useTheme} from '#/alf' -import * as Dialog from '#/components/Dialog' -import {Loader} from '#/components/Loader' -import {Text} from '#/components/Typography' - -interface LikesDialogProps { - control: Dialog.DialogOuterProps['control'] - uri: string -} - -export function LikesDialog(props: LikesDialogProps) { - return ( - <Dialog.Outer control={props.control}> - <Dialog.Handle /> - <LikesDialogInner {...props} /> - </Dialog.Outer> - ) -} - -export function LikesDialogInner({control, uri}: LikesDialogProps) { - const {_} = useLingui() - const t = useTheme() - - const { - data: resolvedUri, - error: resolveError, - isFetched: hasFetchedResolvedUri, - } = useResolveUriQuery(uri) - const { - data, - isFetching: isFetchingLikedBy, - isFetched: hasFetchedLikedBy, - isFetchingNextPage, - hasNextPage, - fetchNextPage, - isError, - error: likedByError, - } = useLikedByQuery(resolvedUri?.uri) - - const isLoading = !hasFetchedResolvedUri || !hasFetchedLikedBy - const likes = useMemo(() => { - if (data?.pages) { - return data.pages.flatMap(page => page.likes) - } - return [] - }, [data]) - - const onEndReached = useCallback(async () => { - if (isFetchingLikedBy || !hasNextPage || isError) return - try { - await fetchNextPage() - } catch (err) { - logger.error('Failed to load more likes', {message: err}) - } - }, [isFetchingLikedBy, hasNextPage, isError, fetchNextPage]) - - const renderItem = useCallback( - ({item}: {item: GetLikes.Like}) => { - return ( - <ProfileCardWithFollowBtn - key={item.actor.did} - profile={item.actor} - onPress={() => control.close()} - /> - ) - }, - [control], - ) - - return ( - <Dialog.Inner label={_(msg`Users that have liked this content or profile`)}> - <Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_lg]}> - <Trans>Liked by</Trans> - </Text> - - {isLoading ? ( - <View style={{minHeight: 300}}> - <Loader size="xl" /> - </View> - ) : resolveError || likedByError || !data ? ( - <ErrorMessage message={cleanError(resolveError || likedByError)} /> - ) : likes.length === 0 ? ( - <View style={[t.atoms.bg_contrast_50, a.px_md, a.py_xl, a.rounded_md]}> - <Text style={[a.text_center]}> - <Trans> - Nobody has liked this yet. Maybe you should be the first! - </Trans> - </Text> - </View> - ) : ( - <FlatList - data={likes} - keyExtractor={item => item.actor.did} - onEndReached={onEndReached} - renderItem={renderItem} - initialNumToRender={15} - ListFooterComponent={ - <ListFooterComponent isFetching={isFetchingNextPage} /> - } - /> - )} - - <Dialog.Close /> - </Dialog.Inner> - ) -} - -function ListFooterComponent({isFetching}: {isFetching: boolean}) { - if (isFetching) { - return ( - <View style={a.pt_lg}> - <ActivityIndicator /> - </View> - ) - } - return null -} diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index beb09cdc7..394ff9946 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -8,15 +8,15 @@ import { import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {getModerationCauseKey} from '#/lib/moderation' import {type LogEvents} from '#/lib/statsig/statsig' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {useSession} from '#/state/session' -import {ProfileCardPills} from '#/view/com/profile/ProfileCard' import * as Toast from '#/view/com/util/Toast' -import {UserAvatar} from '#/view/com/util/UserAvatar' +import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import { Button, @@ -27,6 +27,7 @@ import { import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {Link as InternalLink, type LinkProps} from '#/components/Link' +import * as Pills from '#/components/Pills' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' @@ -35,13 +36,15 @@ export function Default({ profile, moderationOpts, logContext = 'ProfileCard', + testID, }: { profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts logContext?: 'ProfileCard' | 'StarterPackProfilesList' + testID?: string }) { return ( - <Link profile={profile}> + <Link testID={testID} profile={profile}> <Card profile={profile} moderationOpts={moderationOpts} @@ -60,8 +63,6 @@ export function Card({ moderationOpts: ModerationOpts logContext?: 'ProfileCard' | 'StarterPackProfilesList' }) { - const moderation = moderateProfile(profile, moderationOpts) - return ( <Outer> <Header> @@ -74,10 +75,7 @@ export function Card({ /> </Header> - <ProfileCardPills - followedBy={Boolean(profile.viewer?.followedBy)} - moderation={moderation} - /> + <Labels profile={profile} moderationOpts={moderationOpts} /> <Description profile={profile} /> </Outer> @@ -87,7 +85,7 @@ export function Card({ export function Outer({ children, }: { - children: React.ReactElement | React.ReactElement[] + children: React.ReactNode | React.ReactNode[] }) { return <View style={[a.w_full, a.flex_1, a.gap_xs]}>{children}</View> } @@ -95,7 +93,7 @@ export function Outer({ export function Header({ children, }: { - children: React.ReactElement | React.ReactElement[] + children: React.ReactNode | React.ReactNode[] }) { return <View style={[a.flex_row, a.align_center, a.gap_sm]}>{children}</View> } @@ -137,10 +135,9 @@ export function Avatar({ const moderation = moderateProfile(profile, moderationOpts) return ( - <UserAvatar + <PreviewableUserAvatar size={40} - avatar={profile.avatar} - type={profile.associated?.labeler ? 'labeler' : 'user'} + profile={profile} moderation={moderation.ui('avatar')} /> ) @@ -415,3 +412,31 @@ export function FollowButtonInner({ </View> ) } + +export function Labels({ + profile, + moderationOpts, +}: { + profile: bsky.profile.AnyProfileView + moderationOpts: ModerationOpts +}) { + const moderation = moderateProfile(profile, moderationOpts) + const modui = moderation.ui('profileList') + const followedBy = profile.viewer?.followedBy + + if (!followedBy && !modui.inform && !modui.alert) { + return null + } + + return ( + <Pills.Row style={[a.pt_xs]}> + {followedBy && <Pills.FollowsYou />} + {modui.alerts.map(alert => ( + <Pills.Label key={getModerationCauseKey(alert)} cause={alert} /> + ))} + {modui.informs.map(inform => ( + <Pills.Label key={getModerationCauseKey(inform)} cause={inform} /> + ))} + </Pills.Row> + ) +} diff --git a/src/screens/Search/SearchResults.tsx b/src/screens/Search/SearchResults.tsx index bb51d2deb..6b7a582d5 100644 --- a/src/screens/Search/SearchResults.tsx +++ b/src/screens/Search/SearchResults.tsx @@ -275,9 +275,7 @@ let SearchScreenUserResults = ({ {results.length ? ( <List data={results} - renderItem={({item}) => ( - <ProfileCardWithFollowBtn profile={item} noBg /> - )} + renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} />} keyExtractor={item => item.did} desktopFixedHeight contentContainerStyle={{paddingBottom: 100}} diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index da32c2d48..dc18cbe5d 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -1,22 +1,23 @@ import React, {useCallback} from 'react' import {Dimensions, type StyleProp, View, type ViewStyle} from 'react-native' import {type AppBskyGraphDefs} from '@atproto/api' -import {msg} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' import {useModalControls} from '#/state/modals' +import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useListMembersQuery} from '#/state/queries/list-members' import {useSession} from '#/state/session' -import {ProfileCard} from '#/view/com/profile/ProfileCard' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' -import {Button} from '#/view/com/util/forms/Button' import {List, type ListRef} from '#/view/com/util/List' import {ProfileCardFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' +import {atoms as a, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' import {ListFooter} from '#/components/Lists' +import * as ProfileCard from '#/components/ProfileCard' import type * as bsky from '#/types/bsky' const LOADING_ITEM = {_reactKey: '__loading__'} @@ -47,11 +48,12 @@ export function ListMembers({ headerOffset?: number desktopFixedHeightOffset?: number }) { + const t = useTheme() const {_} = useLingui() const [isRefreshing, setIsRefreshing] = React.useState(false) - const {isMobile} = useWebMediaQueries() const {openModal} = useModalControls() const {currentAccount} = useSession() + const moderationOpts = useModerationOpts() const { data, @@ -131,23 +133,6 @@ export function ListMembers({ // rendering // = - const renderMemberButton = React.useCallback( - (profile: bsky.profile.AnyProfileView) => { - if (!isOwner) { - return null - } - return ( - <Button - testID={`user-${profile.handle}-editBtn`} - type="default" - label={_(msg({message: 'Edit', context: 'action'}))} - onPress={() => onPressEditMembership(profile)} - /> - ) - }, - [isOwner, onPressEditMembership, _], - ) - const renderItem = React.useCallback( ({item}: {item: any}) => { if (item === EMPTY_ITEM) { @@ -171,26 +156,60 @@ export function ListMembers({ } else if (item === LOADING_ITEM) { return <ProfileCardFeedLoadingPlaceholder /> } + + const profile = (item as AppBskyGraphDefs.ListItemView).subject + if (!moderationOpts) return null + return ( - <ProfileCard - testID={`user-${ - (item as AppBskyGraphDefs.ListItemView).subject.handle - }`} - profile={(item as AppBskyGraphDefs.ListItemView).subject} - renderButton={renderMemberButton} - style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}} - noModFilter - /> + <View + style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]}> + <ProfileCard.Link profile={profile}> + <ProfileCard.Outer> + <ProfileCard.Header> + <ProfileCard.Avatar + profile={profile} + moderationOpts={moderationOpts} + /> + <ProfileCard.NameAndHandle + profile={profile} + moderationOpts={moderationOpts} + /> + {isOwner && ( + <Button + testID={`user-${profile.handle}-editBtn`} + label={_(msg({message: 'Edit', context: 'action'}))} + onPress={() => onPressEditMembership(profile)} + size="small" + variant="solid" + color="secondary"> + <ButtonText> + <Trans context="action">Edit</Trans> + </ButtonText> + </Button> + )} + </ProfileCard.Header> + + <ProfileCard.Labels + profile={profile} + moderationOpts={moderationOpts} + /> + + <ProfileCard.Description profile={profile} /> + </ProfileCard.Outer> + </ProfileCard.Link> + </View> ) }, [ - renderMemberButton, renderEmptyState, error, onPressTryAgain, onPressRetryLoadMore, - isMobile, + moderationOpts, + isOwner, + onPressEditMembership, _, + t, ], ) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index bd09d6514..cee950703 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,307 +1,36 @@ -import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import { - AppBskyActorDefs, - moderateProfile, - ModerationDecision, -} from '@atproto/api' -import {useQueryClient} from '@tanstack/react-query' +import {View} from 'react-native' +import {type AppBskyActorDefs} from '@atproto/api' -import {usePalette} from '#/lib/hooks/usePalette' -import {getModerationCauseKey, isJustAMute} from '#/lib/moderation' -import {makeProfileLink} from '#/lib/routes/links' -import {sanitizeDisplayName} from '#/lib/strings/display-names' -import {sanitizeHandle} from '#/lib/strings/handles' -import {s} from '#/lib/styles' -import {useProfileShadow} from '#/state/cache/profile-shadow' -import {Shadow} from '#/state/cache/types' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {precacheProfile} from '#/state/queries/profile' -import {useSession} from '#/state/session' -import {atoms as a} from '#/alf' -import { - KnownFollowers, - shouldShowKnownFollowers, -} from '#/components/KnownFollowers' -import * as Pills from '#/components/Pills' -import * as bsky from '#/types/bsky' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' -import {PreviewableUserAvatar} from '../util/UserAvatar' -import {FollowButton} from './FollowButton' - -export function ProfileCard({ - testID, - profile: profileUnshadowed, - noModFilter, - noBg, - noBorder, - renderButton, - onPress, - style, - showKnownFollowers, -}: { - testID?: string - profile: bsky.profile.AnyProfileView - noModFilter?: boolean - noBg?: boolean - noBorder?: boolean - renderButton?: ( - profile: Shadow<bsky.profile.AnyProfileView>, - ) => React.ReactNode - onPress?: () => void - style?: StyleProp<ViewStyle> - showKnownFollowers?: boolean -}) { - const queryClient = useQueryClient() - const pal = usePalette('default') - const profile = useProfileShadow(profileUnshadowed) - const moderationOpts = useModerationOpts() - const isLabeler = profile?.associated?.labeler - - const onBeforePress = React.useCallback(() => { - onPress?.() - precacheProfile(queryClient, profile) - }, [onPress, profile, queryClient]) - - if (!moderationOpts) { - return null - } - const moderation = moderateProfile(profile, moderationOpts) - const modui = moderation.ui('profileList') - if (!noModFilter && modui.filter && !isJustAMute(modui)) { - return null - } - - const knownFollowersVisible = - showKnownFollowers && - shouldShowKnownFollowers(profile.viewer?.knownFollowers) && - moderationOpts - const hasDescription = 'description' in profile - - return ( - <Link - testID={testID} - style={[ - styles.outer, - pal.border, - noBorder && styles.outerNoBorder, - !noBg && pal.view, - style, - ]} - href={makeProfileLink(profile)} - title={profile.handle} - asAnchor - onBeforePress={onBeforePress} - anchorNoUnderline> - <View style={styles.layout}> - <View style={styles.layoutAvi}> - <PreviewableUserAvatar - size={40} - profile={profile} - moderation={moderation.ui('avatar')} - type={isLabeler ? 'labeler' : 'user'} - /> - </View> - <View style={styles.layoutContent}> - <Text - emoji - type="lg" - style={[s.bold, pal.text, a.self_start]} - numberOfLines={1} - lineHeight={1.2}> - {sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - )} - </Text> - <Text emoji type="md" style={[pal.textLight]} numberOfLines={1}> - {sanitizeHandle(profile.handle, '@')} - </Text> - <ProfileCardPills - followedBy={!!profile.viewer?.followedBy} - moderation={moderation} - /> - {!!profile.viewer?.followedBy && <View style={s.flexRow} />} - </View> - {renderButton && !isLabeler ? ( - <View style={styles.layoutButton}>{renderButton(profile)}</View> - ) : undefined} - </View> - {hasDescription || knownFollowersVisible ? ( - <View style={styles.details}> - {hasDescription && profile.description ? ( - <Text emoji style={pal.text} numberOfLines={4}> - {profile.description as string} - </Text> - ) : null} - {knownFollowersVisible ? ( - <View - style={[ - a.flex_row, - a.align_center, - a.gap_sm, - !!hasDescription && a.mt_md, - ]}> - <KnownFollowers - minimal - profile={profile} - moderationOpts={moderationOpts} - /> - </View> - ) : null} - </View> - ) : null} - </Link> - ) -} - -export function ProfileCardPills({ - followedBy, - moderation, -}: { - followedBy: boolean - moderation: ModerationDecision -}) { - const modui = moderation.ui('profileList') - if (!followedBy && !modui.inform && !modui.alert) { - return null - } - - return ( - <Pills.Row style={[a.pt_xs]}> - {followedBy && <Pills.FollowsYou />} - {modui.alerts.map(alert => ( - <Pills.Label key={getModerationCauseKey(alert)} cause={alert} /> - ))} - {modui.informs.map(inform => ( - <Pills.Label key={getModerationCauseKey(inform)} cause={inform} /> - ))} - </Pills.Row> - ) -} +import {atoms as a, useTheme} from '#/alf' +import * as ProfileCard from '#/components/ProfileCard' export function ProfileCardWithFollowBtn({ profile, - noBg, noBorder, - onPress, - onFollow, logContext = 'ProfileCard', - showKnownFollowers, }: { profile: AppBskyActorDefs.ProfileView - noBg?: boolean noBorder?: boolean - onPress?: () => void - onFollow?: () => void logContext?: 'ProfileCard' | 'StarterPackProfilesList' - showKnownFollowers?: boolean }) { - const {currentAccount} = useSession() - const isMe = profile.did === currentAccount?.did + const t = useTheme() + const moderationOpts = useModerationOpts() + + if (!moderationOpts) return null return ( - <ProfileCard - profile={profile} - noBg={noBg} - noBorder={noBorder} - renderButton={ - isMe - ? undefined - : profileShadow => ( - <FollowButton - profile={profileShadow} - logContext={logContext} - onFollow={onFollow} - /> - ) - } - onPress={onPress} - showKnownFollowers={!isMe && showKnownFollowers} - /> + <View + style={[ + a.py_md, + a.px_xl, + !noBorder && [a.border_t, t.atoms.border_contrast_low], + ]}> + <ProfileCard.Default + profile={profile} + moderationOpts={moderationOpts} + logContext={logContext} + /> + </View> ) } - -const styles = StyleSheet.create({ - outer: { - borderTopWidth: StyleSheet.hairlineWidth, - paddingHorizontal: 6, - paddingVertical: 4, - }, - outerNoBorder: { - borderTopWidth: 0, - }, - layout: { - flexDirection: 'row', - alignItems: 'center', - }, - layoutAvi: { - alignSelf: 'flex-start', - width: 54, - paddingLeft: 4, - paddingTop: 10, - }, - avi: { - width: 40, - height: 40, - borderRadius: 20, - resizeMode: 'cover', - }, - layoutContent: { - flex: 1, - paddingRight: 10, - paddingTop: 10, - paddingBottom: 10, - }, - layoutButton: { - paddingRight: 10, - }, - details: { - justifyContent: 'center', - paddingLeft: 54, - paddingRight: 10, - paddingBottom: 10, - }, - pills: { - alignItems: 'flex-start', - flexDirection: 'row', - flexWrap: 'wrap', - columnGap: 6, - rowGap: 2, - }, - pill: { - borderRadius: 4, - paddingHorizontal: 6, - paddingVertical: 2, - justifyContent: 'center', - }, - btn: { - paddingVertical: 7, - borderRadius: 50, - marginLeft: 6, - paddingHorizontal: 14, - }, - - followedBy: { - flexDirection: 'row', - paddingLeft: 54, - paddingRight: 20, - marginBottom: 10, - marginTop: -6, - }, - followedByAviContainer: { - width: 24, - height: 36, - }, - followedByAvi: { - width: 36, - height: 36, - borderRadius: 18, - padding: 2, - }, - followsByDesc: { - flex: 1, - paddingRight: 10, - }, -}) diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index 9774c644c..c3a82ac8e 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -1,29 +1,32 @@ -/* eslint-disable no-restricted-imports */ import React from 'react' import {View} from 'react-native' import { - AppBskyActorDefs, - AppBskyFeedDefs, - AppBskyFeedPost, - ComAtprotoLabelDefs, + type AppBskyActorDefs, + type AppBskyFeedDefs, + type AppBskyFeedPost, + type ComAtprotoLabelDefs, interpretLabelValueDefinition, - LabelPreference, + type LabelPreference, LABELS, mock, moderatePost, moderateProfile, - ModerationBehavior, - ModerationDecision, - ModerationOpts, + type ModerationBehavior, + type ModerationDecision, + type ModerationOpts, RichText, } from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' -import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' +import { + type CommonNavigatorParams, + type NativeStackScreenProps, +} from '#/lib/routes/types' +import {useModerationOpts} from '#/state/preferences/moderation-opts' import {moderationOptsOverrideContext} from '#/state/preferences/moderation-opts' -import {FeedNotification} from '#/state/queries/notifications/types' +import {type FeedNotification} from '#/state/queries/notifications/types' import { groupNotifications, shouldFilterNotif, @@ -42,12 +45,12 @@ import { ChevronTop_Stroke2_Corner0_Rounded as ChevronTop, } from '#/components/icons/Chevron' import * as Layout from '#/components/Layout' +import * as ProfileCard from '#/components/ProfileCard' import {H1, H3, P, Text} from '#/components/Typography' import {ScreenHider} from '../../components/moderation/ScreenHider' import {NotificationFeedItem} from '../com/notifications/NotificationFeedItem' import {PostThreadItem} from '../com/post-thread/PostThreadItem' import {PostFeedItem} from '../com/posts/PostFeedItem' -import {ProfileCard} from '../com/profile/ProfileCard' const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys( LABELS, @@ -890,6 +893,9 @@ function MockAccountCard({ moderation: ModerationDecision }) { const t = useTheme() + const moderationOpts = useModerationOpts() + + if (!moderationOpts) return null if (moderation.ui('profileList').filter) { return ( @@ -899,7 +905,7 @@ function MockAccountCard({ ) } - return <ProfileCard profile={profile} /> + return <ProfileCard.Card profile={profile} moderationOpts={moderationOpts} /> } function MockAccountScreen({ diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index c2f87c086..cefa29f6c 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -6,35 +6,38 @@ import { StyleSheet, View, } from 'react-native' -import {AppBskyActorDefs as ActorDefs} from '@atproto/api' +import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' -import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {CommonNavigatorParams} from '#/lib/routes/types' +import {type CommonNavigatorParams} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' +import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts' import {useSetMinimalShellMode} from '#/state/shell' -import {ProfileCard} from '#/view/com/profile/ProfileCard' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {Text} from '#/view/com/util/text/Text' import {ViewHeader} from '#/view/com/util/ViewHeader' -import {atoms as a} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import * as Layout from '#/components/Layout' +import * as ProfileCard from '#/components/ProfileCard' type Props = NativeStackScreenProps< CommonNavigatorParams, 'ModerationBlockedAccounts' > export function ModerationBlockedAccounts({}: Props) { + const t = useTheme() const pal = usePalette('default') const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() const {isTabletOrDesktop} = useWebMediaQueries() + const moderationOpts = useModerationOpts() const [isPTRing, setIsPTRing] = React.useState(false) const { @@ -87,14 +90,20 @@ export function ModerationBlockedAccounts({}: Props) { }: { item: ActorDefs.ProfileView index: number - }) => ( - <ProfileCard - testID={`blockedAccount-${index}`} - key={item.did} - profile={item} - noModFilter - /> - ) + }) => { + if (!moderationOpts) return null + return ( + <View + style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]} + key={item.did}> + <ProfileCard.Default + testID={`blockedAccount-${index}`} + profile={item} + moderationOpts={moderationOpts} + /> + </View> + ) + } return ( <Layout.Screen testID="blockedAccountsScreen"> <Layout.Center style={[a.flex_1, {paddingBottom: 100}]}> diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 059985631..f49337b7c 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -6,35 +6,38 @@ import { StyleSheet, View, } from 'react-native' -import {AppBskyActorDefs as ActorDefs} from '@atproto/api' +import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' -import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {CommonNavigatorParams} from '#/lib/routes/types' +import {type CommonNavigatorParams} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' +import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts' import {useSetMinimalShellMode} from '#/state/shell' -import {ProfileCard} from '#/view/com/profile/ProfileCard' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {Text} from '#/view/com/util/text/Text' import {ViewHeader} from '#/view/com/util/ViewHeader' -import {atoms as a} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import * as Layout from '#/components/Layout' +import * as ProfileCard from '#/components/ProfileCard' type Props = NativeStackScreenProps< CommonNavigatorParams, 'ModerationMutedAccounts' > export function ModerationMutedAccounts({}: Props) { + const t = useTheme() const pal = usePalette('default') const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() const {isTabletOrDesktop} = useWebMediaQueries() + const moderationOpts = useModerationOpts() const [isPTRing, setIsPTRing] = React.useState(false) const { @@ -87,14 +90,34 @@ export function ModerationMutedAccounts({}: Props) { }: { item: ActorDefs.ProfileView index: number - }) => ( - <ProfileCard - testID={`mutedAccount-${index}`} - key={item.did} - profile={item} - noModFilter - /> - ) + }) => { + if (!moderationOpts) return null + return ( + <View + style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]} + key={item.did}> + <ProfileCard.Link profile={item} testID={`mutedAccount-${index}`}> + <ProfileCard.Outer> + <ProfileCard.Header> + <ProfileCard.Avatar + profile={item} + moderationOpts={moderationOpts} + /> + <ProfileCard.NameAndHandle + profile={item} + moderationOpts={moderationOpts} + /> + </ProfileCard.Header> + <ProfileCard.Labels + profile={item} + moderationOpts={moderationOpts} + /> + <ProfileCard.Description profile={item} /> + </ProfileCard.Outer> + </ProfileCard.Link> + </View> + ) + } return ( <Layout.Screen testID="mutedAccountsScreen"> <ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop /> |