[Chat] Add message item blocked state (#10616)
This commit is contained in:
@@ -26,18 +26,25 @@ import {plural} from '@lingui/core/macro'
|
|||||||
import {Trans, useLingui} from '@lingui/react/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
|
||||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
|
import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
|
import {type Shadow} from '#/state/cache/types'
|
||||||
import {type ConvoItem} from '#/state/messages/convo/types'
|
import {type ConvoItem} from '#/state/messages/convo/types'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
|
import {useProfileBlockMutationQueue} from '#/state/queries/profile'
|
||||||
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
|
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {atoms as a, native, platform, useTheme} from '#/alf'
|
import {atoms as a, native, platform, useTheme} from '#/alf'
|
||||||
import {isOnlyEmoji} from '#/alf/typography'
|
import {isOnlyEmoji} from '#/alf/typography'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
|
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
|
||||||
import {InlineLinkText, Link} from '#/components/Link'
|
import {InlineLinkText, Link} from '#/components/Link'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {DateDivider} from './DateDivider'
|
import {DateDivider} from './DateDivider'
|
||||||
@@ -109,7 +116,7 @@ let MessageItem = ({
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const {message} = item
|
const {message} = item
|
||||||
const profile = relatedProfiles.get(message.sender.did)
|
const profile = useMaybeProfileShadow(relatedProfiles.get(message.sender.did))
|
||||||
|
|
||||||
const reactionsControl = useDialogControl()
|
const reactionsControl = useDialogControl()
|
||||||
|
|
||||||
@@ -433,70 +440,76 @@ let MessageItem = ({
|
|||||||
{displayName}
|
{displayName}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
<ActionsWrapper
|
{profile && isBlockedOrBlocking(profile) && isGroupChat ? (
|
||||||
hasReactions={hasReactions}
|
<BlockedPlaceholder profile={profile} style={borderRadiusStyle} />
|
||||||
isFromSelf={isFromSelf}
|
) : (
|
||||||
message={message}
|
<ActionsWrapper
|
||||||
senderProfile={profile}
|
hasReactions={hasReactions}
|
||||||
moderationOpts={moderationOpts}>
|
isFromSelf={isFromSelf}
|
||||||
{AppBskyEmbedRecord.isView(message.embed) && (
|
message={message}
|
||||||
<MessageItemEmbed
|
senderProfile={profile}
|
||||||
embed={message.embed}
|
moderationOpts={moderationOpts}>
|
||||||
isFromSelf={isFromSelf}
|
{AppBskyEmbedRecord.isView(message.embed) && (
|
||||||
isGroupChat={isGroupChat}
|
<MessageItemEmbed
|
||||||
squaredBottomCorner={squaredBottomCorner || hasEmbedAndText}
|
embed={message.embed}
|
||||||
squaredTopCorner={squaredTopCorner}
|
isFromSelf={isFromSelf}
|
||||||
/>
|
isGroupChat={isGroupChat}
|
||||||
)}
|
squaredBottomCorner={squaredBottomCorner || hasEmbedAndText}
|
||||||
{rt.text.length > 0 && (
|
squaredTopCorner={squaredTopCorner}
|
||||||
<Animated.View
|
|
||||||
accessibilityHint={l`Double tap or long press the message to add a reaction`}
|
|
||||||
style={[
|
|
||||||
!isFromSelf && isGroupChat && a.ml_sm,
|
|
||||||
!isOnlyEmoji(message.text) && [
|
|
||||||
a.rounded_xl,
|
|
||||||
a.py_sm,
|
|
||||||
a.px_md,
|
|
||||||
{
|
|
||||||
marginTop: hasEmbedAndText ? CLUSTERED_MESSAGE_GAP : 0,
|
|
||||||
backgroundColor: isFromSelf
|
|
||||||
? isPending
|
|
||||||
? pendingColor
|
|
||||||
: t.palette.primary_500
|
|
||||||
: t.palette.contrast_50,
|
|
||||||
},
|
|
||||||
isFromSelf ? a.self_end : a.self_start,
|
|
||||||
borderRadiusStyle,
|
|
||||||
],
|
|
||||||
]}>
|
|
||||||
<RichText
|
|
||||||
value={rt}
|
|
||||||
style={[
|
|
||||||
a.text_md,
|
|
||||||
isFromSelf && {color: t.palette.white},
|
|
||||||
// Emoji-only: add top leading to avoid clipping the
|
|
||||||
// glyph, then pull the bottom up by the same amount so
|
|
||||||
// the glyph bottom-aligns with the avatar instead of
|
|
||||||
// sitting above its line-box baseline.
|
|
||||||
isOnlyEmoji(message.text) && [
|
|
||||||
a.leading_tight,
|
|
||||||
// Visually align bottom of the emoji with the avatar
|
|
||||||
!isFromSelf &&
|
|
||||||
platform({
|
|
||||||
android: {marginTop: a.mt_2xs.marginTop},
|
|
||||||
default: {marginBottom: -a.mb_sm.marginBottom},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
]}
|
|
||||||
interactiveStyle={a.underline}
|
|
||||||
enableTags
|
|
||||||
emojiMultiplier={3}
|
|
||||||
shouldProxyLinks={true}
|
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
)}
|
||||||
)}
|
{rt.text.length > 0 && (
|
||||||
{appliedReactions}
|
<Animated.View
|
||||||
</ActionsWrapper>
|
accessibilityHint={l`Double tap or long press the message to add a reaction`}
|
||||||
|
style={[
|
||||||
|
!isFromSelf && isGroupChat && a.ml_sm,
|
||||||
|
!isOnlyEmoji(message.text) && [
|
||||||
|
a.rounded_xl,
|
||||||
|
a.py_sm,
|
||||||
|
a.px_md,
|
||||||
|
{
|
||||||
|
marginTop: hasEmbedAndText
|
||||||
|
? CLUSTERED_MESSAGE_GAP
|
||||||
|
: 0,
|
||||||
|
backgroundColor: isFromSelf
|
||||||
|
? isPending
|
||||||
|
? pendingColor
|
||||||
|
: t.palette.primary_500
|
||||||
|
: t.palette.contrast_50,
|
||||||
|
},
|
||||||
|
isFromSelf ? a.self_end : a.self_start,
|
||||||
|
borderRadiusStyle,
|
||||||
|
],
|
||||||
|
]}>
|
||||||
|
<RichText
|
||||||
|
value={rt}
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
isFromSelf && {color: t.palette.white},
|
||||||
|
// Emoji-only: add top leading to avoid clipping the
|
||||||
|
// glyph, then pull the bottom up by the same amount so
|
||||||
|
// the glyph bottom-aligns with the avatar instead of
|
||||||
|
// sitting above its line-box baseline.
|
||||||
|
isOnlyEmoji(message.text) && [
|
||||||
|
a.leading_tight,
|
||||||
|
// Visually align bottom of the emoji with the avatar
|
||||||
|
!isFromSelf &&
|
||||||
|
platform({
|
||||||
|
android: {marginTop: a.mt_2xs.marginTop},
|
||||||
|
default: {marginBottom: -a.mb_sm.marginBottom},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
interactiveStyle={a.underline}
|
||||||
|
enableTags
|
||||||
|
emojiMultiplier={3}
|
||||||
|
shouldProxyLinks={true}
|
||||||
|
/>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
|
{appliedReactions}
|
||||||
|
</ActionsWrapper>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{isLastInCluster && (
|
{isLastInCluster && (
|
||||||
@@ -560,3 +573,99 @@ let MessageItemMetadata = ({
|
|||||||
}
|
}
|
||||||
MessageItemMetadata = memo(MessageItemMetadata)
|
MessageItemMetadata = memo(MessageItemMetadata)
|
||||||
export {MessageItemMetadata}
|
export {MessageItemMetadata}
|
||||||
|
|
||||||
|
function BlockedPlaceholder({
|
||||||
|
profile,
|
||||||
|
style,
|
||||||
|
}: {
|
||||||
|
profile: Shadow<ChatBskyActorDefs.ProfileViewBasic>
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
}) {
|
||||||
|
const {t: l} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
const control = Prompt.usePromptControl()
|
||||||
|
const [_queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
style={[{maxWidth: '80%'}, a.self_start]}
|
||||||
|
label={
|
||||||
|
profile.viewer?.blocking
|
||||||
|
? l`This message is hidden because you are blocking this user.`
|
||||||
|
: l`This message is hidden because this user is blocking you.`
|
||||||
|
}
|
||||||
|
accessibilityHint={l`Tap for details`}
|
||||||
|
onPress={() => control.open()}>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
a.ml_sm,
|
||||||
|
a.rounded_xl,
|
||||||
|
a.py_sm,
|
||||||
|
a.px_md,
|
||||||
|
t.atoms.bg,
|
||||||
|
a.self_start,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_high,
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
a.leading_snug,
|
||||||
|
a.italic,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
{profile.viewer?.blocking ? (
|
||||||
|
<Trans>
|
||||||
|
This message is hidden because you are blocking this user.
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
This message is hidden because this user is blocking you.
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Animated.View>
|
||||||
|
</Button>
|
||||||
|
<Prompt.Outer control={control}>
|
||||||
|
<Prompt.Content>
|
||||||
|
<Prompt.TitleText>
|
||||||
|
{profile.viewer?.blocking ? (
|
||||||
|
<Trans>
|
||||||
|
You are blocking {sanitizeHandle(profile.handle, '@')}
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
{sanitizeHandle(profile.handle, '@')} is blocking you
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</Prompt.TitleText>
|
||||||
|
<Prompt.DescriptionText>
|
||||||
|
{profile.viewer?.blocking ? (
|
||||||
|
<Trans>
|
||||||
|
Messages from this person are hidden while you are blocking
|
||||||
|
them.
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>
|
||||||
|
Messages from this person are hidden while they are blocking
|
||||||
|
you.
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</Prompt.DescriptionText>
|
||||||
|
<Prompt.Actions>
|
||||||
|
<Prompt.Action onPress={() => {}} cta={l`Okay`} color="primary" />
|
||||||
|
{profile.viewer?.blocking && !profile.viewer.blockingByList && (
|
||||||
|
<Prompt.Action
|
||||||
|
onPress={() => queueUnblock()}
|
||||||
|
cta={l`Unblock`}
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Prompt.Actions>
|
||||||
|
</Prompt.Content>
|
||||||
|
</Prompt.Outer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user