Replace old ProfileCard with new (#8195)
* Replace usages of old ProfileCard * Replace Pills with Labels component * Replace impl of ProfileCardWithFollowButton * Remove never-used LikesDialog * Handle missing mod opts * Add missing profile hover * use modern button in listmembers * remove follow button from muted accounts list --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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}]}>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user