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 {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {getModerationCauseKey} from '#/lib/moderation'
|
||||||
import {type LogEvents} from '#/lib/statsig/statsig'
|
import {type LogEvents} from '#/lib/statsig/statsig'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
|
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {ProfileCardPills} from '#/view/com/profile/ProfileCard'
|
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
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 {atoms as a, useTheme} from '#/alf'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||||
import {Link as InternalLink, type LinkProps} from '#/components/Link'
|
import {Link as InternalLink, type LinkProps} from '#/components/Link'
|
||||||
|
import * as Pills from '#/components/Pills'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
@@ -35,13 +36,15 @@ export function Default({
|
|||||||
profile,
|
profile,
|
||||||
moderationOpts,
|
moderationOpts,
|
||||||
logContext = 'ProfileCard',
|
logContext = 'ProfileCard',
|
||||||
|
testID,
|
||||||
}: {
|
}: {
|
||||||
profile: bsky.profile.AnyProfileView
|
profile: bsky.profile.AnyProfileView
|
||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||||
|
testID?: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Link profile={profile}>
|
<Link testID={testID} profile={profile}>
|
||||||
<Card
|
<Card
|
||||||
profile={profile}
|
profile={profile}
|
||||||
moderationOpts={moderationOpts}
|
moderationOpts={moderationOpts}
|
||||||
@@ -60,8 +63,6 @@ export function Card({
|
|||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||||
}) {
|
}) {
|
||||||
const moderation = moderateProfile(profile, moderationOpts)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Outer>
|
<Outer>
|
||||||
<Header>
|
<Header>
|
||||||
@@ -74,10 +75,7 @@ export function Card({
|
|||||||
/>
|
/>
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<ProfileCardPills
|
<Labels profile={profile} moderationOpts={moderationOpts} />
|
||||||
followedBy={Boolean(profile.viewer?.followedBy)}
|
|
||||||
moderation={moderation}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Description profile={profile} />
|
<Description profile={profile} />
|
||||||
</Outer>
|
</Outer>
|
||||||
@@ -87,7 +85,7 @@ export function Card({
|
|||||||
export function Outer({
|
export function Outer({
|
||||||
children,
|
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>
|
return <View style={[a.w_full, a.flex_1, a.gap_xs]}>{children}</View>
|
||||||
}
|
}
|
||||||
@@ -95,7 +93,7 @@ export function Outer({
|
|||||||
export function Header({
|
export function Header({
|
||||||
children,
|
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>
|
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)
|
const moderation = moderateProfile(profile, moderationOpts)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserAvatar
|
<PreviewableUserAvatar
|
||||||
size={40}
|
size={40}
|
||||||
avatar={profile.avatar}
|
profile={profile}
|
||||||
type={profile.associated?.labeler ? 'labeler' : 'user'}
|
|
||||||
moderation={moderation.ui('avatar')}
|
moderation={moderation.ui('avatar')}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -415,3 +412,31 @@ export function FollowButtonInner({
|
|||||||
</View>
|
</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 ? (
|
{results.length ? (
|
||||||
<List
|
<List
|
||||||
data={results}
|
data={results}
|
||||||
renderItem={({item}) => (
|
renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} />}
|
||||||
<ProfileCardWithFollowBtn profile={item} noBg />
|
|
||||||
)}
|
|
||||||
keyExtractor={item => item.did}
|
keyExtractor={item => item.did}
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
contentContainerStyle={{paddingBottom: 100}}
|
contentContainerStyle={{paddingBottom: 100}}
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import React, {useCallback} from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {Dimensions, type StyleProp, View, type ViewStyle} from 'react-native'
|
import {Dimensions, type StyleProp, View, type ViewStyle} from 'react-native'
|
||||||
import {type AppBskyGraphDefs} from '@atproto/api'
|
import {type AppBskyGraphDefs} from '@atproto/api'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useListMembersQuery} from '#/state/queries/list-members'
|
import {useListMembersQuery} from '#/state/queries/list-members'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {ProfileCard} from '#/view/com/profile/ProfileCard'
|
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
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 {List, type ListRef} from '#/view/com/util/List'
|
||||||
import {ProfileCardFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
import {ProfileCardFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
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 {ListFooter} from '#/components/Lists'
|
||||||
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
import type * as bsky from '#/types/bsky'
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||||
@@ -47,11 +48,12 @@ export function ListMembers({
|
|||||||
headerOffset?: number
|
headerOffset?: number
|
||||||
desktopFixedHeightOffset?: number
|
desktopFixedHeightOffset?: number
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const [isRefreshing, setIsRefreshing] = React.useState(false)
|
const [isRefreshing, setIsRefreshing] = React.useState(false)
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -131,23 +133,6 @@ export function ListMembers({
|
|||||||
// rendering
|
// 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(
|
const renderItem = React.useCallback(
|
||||||
({item}: {item: any}) => {
|
({item}: {item: any}) => {
|
||||||
if (item === EMPTY_ITEM) {
|
if (item === EMPTY_ITEM) {
|
||||||
@@ -171,26 +156,60 @@ export function ListMembers({
|
|||||||
} else if (item === LOADING_ITEM) {
|
} else if (item === LOADING_ITEM) {
|
||||||
return <ProfileCardFeedLoadingPlaceholder />
|
return <ProfileCardFeedLoadingPlaceholder />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profile = (item as AppBskyGraphDefs.ListItemView).subject
|
||||||
|
if (!moderationOpts) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfileCard
|
<View
|
||||||
testID={`user-${
|
style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]}>
|
||||||
(item as AppBskyGraphDefs.ListItemView).subject.handle
|
<ProfileCard.Link profile={profile}>
|
||||||
}`}
|
<ProfileCard.Outer>
|
||||||
profile={(item as AppBskyGraphDefs.ListItemView).subject}
|
<ProfileCard.Header>
|
||||||
renderButton={renderMemberButton}
|
<ProfileCard.Avatar
|
||||||
style={{paddingHorizontal: isMobile ? 8 : 14, paddingVertical: 4}}
|
profile={profile}
|
||||||
noModFilter
|
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,
|
renderEmptyState,
|
||||||
error,
|
error,
|
||||||
onPressTryAgain,
|
onPressTryAgain,
|
||||||
onPressRetryLoadMore,
|
onPressRetryLoadMore,
|
||||||
isMobile,
|
moderationOpts,
|
||||||
|
isOwner,
|
||||||
|
onPressEditMembership,
|
||||||
_,
|
_,
|
||||||
|
t,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,307 +1,36 @@
|
|||||||
import React from 'react'
|
import {View} from 'react-native'
|
||||||
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
import {type AppBskyActorDefs} from '@atproto/api'
|
||||||
import {
|
|
||||||
AppBskyActorDefs,
|
|
||||||
moderateProfile,
|
|
||||||
ModerationDecision,
|
|
||||||
} from '@atproto/api'
|
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
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 {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {precacheProfile} from '#/state/queries/profile'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useSession} from '#/state/session'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ProfileCardWithFollowBtn({
|
export function ProfileCardWithFollowBtn({
|
||||||
profile,
|
profile,
|
||||||
noBg,
|
|
||||||
noBorder,
|
noBorder,
|
||||||
onPress,
|
|
||||||
onFollow,
|
|
||||||
logContext = 'ProfileCard',
|
logContext = 'ProfileCard',
|
||||||
showKnownFollowers,
|
|
||||||
}: {
|
}: {
|
||||||
profile: AppBskyActorDefs.ProfileView
|
profile: AppBskyActorDefs.ProfileView
|
||||||
noBg?: boolean
|
|
||||||
noBorder?: boolean
|
noBorder?: boolean
|
||||||
onPress?: () => void
|
|
||||||
onFollow?: () => void
|
|
||||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||||
showKnownFollowers?: boolean
|
|
||||||
}) {
|
}) {
|
||||||
const {currentAccount} = useSession()
|
const t = useTheme()
|
||||||
const isMe = profile.did === currentAccount?.did
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
|
if (!moderationOpts) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfileCard
|
<View
|
||||||
profile={profile}
|
style={[
|
||||||
noBg={noBg}
|
a.py_md,
|
||||||
noBorder={noBorder}
|
a.px_xl,
|
||||||
renderButton={
|
!noBorder && [a.border_t, t.atoms.border_contrast_low],
|
||||||
isMe
|
]}>
|
||||||
? undefined
|
<ProfileCard.Default
|
||||||
: profileShadow => (
|
profile={profile}
|
||||||
<FollowButton
|
moderationOpts={moderationOpts}
|
||||||
profile={profileShadow}
|
logContext={logContext}
|
||||||
logContext={logContext}
|
/>
|
||||||
onFollow={onFollow}
|
</View>
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onPress={onPress}
|
|
||||||
showKnownFollowers={!isMe && showKnownFollowers}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {
|
import {
|
||||||
AppBskyActorDefs,
|
type AppBskyActorDefs,
|
||||||
AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
type AppBskyFeedPost,
|
||||||
ComAtprotoLabelDefs,
|
type ComAtprotoLabelDefs,
|
||||||
interpretLabelValueDefinition,
|
interpretLabelValueDefinition,
|
||||||
LabelPreference,
|
type LabelPreference,
|
||||||
LABELS,
|
LABELS,
|
||||||
mock,
|
mock,
|
||||||
moderatePost,
|
moderatePost,
|
||||||
moderateProfile,
|
moderateProfile,
|
||||||
ModerationBehavior,
|
type ModerationBehavior,
|
||||||
ModerationDecision,
|
type ModerationDecision,
|
||||||
ModerationOpts,
|
type ModerationOpts,
|
||||||
RichText,
|
RichText,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
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 {moderationOptsOverrideContext} from '#/state/preferences/moderation-opts'
|
||||||
import {FeedNotification} from '#/state/queries/notifications/types'
|
import {type FeedNotification} from '#/state/queries/notifications/types'
|
||||||
import {
|
import {
|
||||||
groupNotifications,
|
groupNotifications,
|
||||||
shouldFilterNotif,
|
shouldFilterNotif,
|
||||||
@@ -42,12 +45,12 @@ import {
|
|||||||
ChevronTop_Stroke2_Corner0_Rounded as ChevronTop,
|
ChevronTop_Stroke2_Corner0_Rounded as ChevronTop,
|
||||||
} from '#/components/icons/Chevron'
|
} from '#/components/icons/Chevron'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
import {H1, H3, P, Text} from '#/components/Typography'
|
import {H1, H3, P, Text} from '#/components/Typography'
|
||||||
import {ScreenHider} from '../../components/moderation/ScreenHider'
|
import {ScreenHider} from '../../components/moderation/ScreenHider'
|
||||||
import {NotificationFeedItem} from '../com/notifications/NotificationFeedItem'
|
import {NotificationFeedItem} from '../com/notifications/NotificationFeedItem'
|
||||||
import {PostThreadItem} from '../com/post-thread/PostThreadItem'
|
import {PostThreadItem} from '../com/post-thread/PostThreadItem'
|
||||||
import {PostFeedItem} from '../com/posts/PostFeedItem'
|
import {PostFeedItem} from '../com/posts/PostFeedItem'
|
||||||
import {ProfileCard} from '../com/profile/ProfileCard'
|
|
||||||
|
|
||||||
const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys(
|
const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys(
|
||||||
LABELS,
|
LABELS,
|
||||||
@@ -890,6 +893,9 @@ function MockAccountCard({
|
|||||||
moderation: ModerationDecision
|
moderation: ModerationDecision
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
|
if (!moderationOpts) return null
|
||||||
|
|
||||||
if (moderation.ui('profileList').filter) {
|
if (moderation.ui('profileList').filter) {
|
||||||
return (
|
return (
|
||||||
@@ -899,7 +905,7 @@ function MockAccountCard({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ProfileCard profile={profile} />
|
return <ProfileCard.Card profile={profile} moderationOpts={moderationOpts} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function MockAccountScreen({
|
function MockAccountScreen({
|
||||||
|
|||||||
@@ -6,35 +6,38 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} 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 {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
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 {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
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 {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts'
|
import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {ProfileCard} from '#/view/com/profile/ProfileCard'
|
|
||||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
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 Layout from '#/components/Layout'
|
||||||
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
CommonNavigatorParams,
|
CommonNavigatorParams,
|
||||||
'ModerationBlockedAccounts'
|
'ModerationBlockedAccounts'
|
||||||
>
|
>
|
||||||
export function ModerationBlockedAccounts({}: Props) {
|
export function ModerationBlockedAccounts({}: Props) {
|
||||||
|
const t = useTheme()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||||
const {
|
const {
|
||||||
@@ -87,14 +90,20 @@ export function ModerationBlockedAccounts({}: Props) {
|
|||||||
}: {
|
}: {
|
||||||
item: ActorDefs.ProfileView
|
item: ActorDefs.ProfileView
|
||||||
index: number
|
index: number
|
||||||
}) => (
|
}) => {
|
||||||
<ProfileCard
|
if (!moderationOpts) return null
|
||||||
testID={`blockedAccount-${index}`}
|
return (
|
||||||
key={item.did}
|
<View
|
||||||
profile={item}
|
style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]}
|
||||||
noModFilter
|
key={item.did}>
|
||||||
/>
|
<ProfileCard.Default
|
||||||
)
|
testID={`blockedAccount-${index}`}
|
||||||
|
profile={item}
|
||||||
|
moderationOpts={moderationOpts}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="blockedAccountsScreen">
|
<Layout.Screen testID="blockedAccountsScreen">
|
||||||
<Layout.Center style={[a.flex_1, {paddingBottom: 100}]}>
|
<Layout.Center style={[a.flex_1, {paddingBottom: 100}]}>
|
||||||
|
|||||||
@@ -6,35 +6,38 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} 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 {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
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 {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
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 {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts'
|
import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {ProfileCard} from '#/view/com/profile/ProfileCard'
|
|
||||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
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 Layout from '#/components/Layout'
|
||||||
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
CommonNavigatorParams,
|
CommonNavigatorParams,
|
||||||
'ModerationMutedAccounts'
|
'ModerationMutedAccounts'
|
||||||
>
|
>
|
||||||
export function ModerationMutedAccounts({}: Props) {
|
export function ModerationMutedAccounts({}: Props) {
|
||||||
|
const t = useTheme()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||||
|
const moderationOpts = useModerationOpts()
|
||||||
|
|
||||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||||
const {
|
const {
|
||||||
@@ -87,14 +90,34 @@ export function ModerationMutedAccounts({}: Props) {
|
|||||||
}: {
|
}: {
|
||||||
item: ActorDefs.ProfileView
|
item: ActorDefs.ProfileView
|
||||||
index: number
|
index: number
|
||||||
}) => (
|
}) => {
|
||||||
<ProfileCard
|
if (!moderationOpts) return null
|
||||||
testID={`mutedAccount-${index}`}
|
return (
|
||||||
key={item.did}
|
<View
|
||||||
profile={item}
|
style={[a.py_md, a.px_xl, a.border_t, t.atoms.border_contrast_low]}
|
||||||
noModFilter
|
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 (
|
return (
|
||||||
<Layout.Screen testID="mutedAccountsScreen">
|
<Layout.Screen testID="mutedAccountsScreen">
|
||||||
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
||||||
|
|||||||
Reference in New Issue
Block a user