Show message when chat is locked or ended (#10413)
This commit is contained in:
@@ -15,7 +15,6 @@ import {
|
||||
Unlock_Stroke2_Corner2_Rounded as UnlockIcon,
|
||||
} from '#/components/icons/Lock'
|
||||
import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
|
||||
|
||||
export type SystemMessageInfo = {
|
||||
message: MessageDescriptor
|
||||
@@ -69,13 +68,7 @@ export function getSystemMessageInfo(
|
||||
} else if (ChatBskyConvoDefs.isSystemMessageDataUnlockConvo(data)) {
|
||||
return {Icon: UnlockIcon, message: msg`Chat unlocked`}
|
||||
} else if (ChatBskyConvoDefs.isSystemMessageDataLockConvoPermanently(data)) {
|
||||
const name = getReferredDisplayName(data.lockedBy, relatedProfiles)
|
||||
return {
|
||||
Icon: XIcon,
|
||||
message: name
|
||||
? msg`${name} ended the group chat`
|
||||
: msg`This group chat was ended`,
|
||||
}
|
||||
return {Icon: LockIcon, message: msg`Chat ended`}
|
||||
} else if (ChatBskyConvoDefs.isSystemMessageDataEditGroup(data)) {
|
||||
return {
|
||||
Icon: PencilIcon,
|
||||
|
||||
@@ -51,6 +51,8 @@ import {Text} from '#/components/Typography'
|
||||
import {useAnalytics} from '#/analytics'
|
||||
import {IS_INTERNAL, IS_LIQUID_GLASS, IS_WEB} from '#/env'
|
||||
import {ChatDisabled} from './components/ChatDisabled'
|
||||
import {ChatEnded} from './components/ChatEnded'
|
||||
import {ChatLocked} from './components/ChatLocked'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -262,6 +264,26 @@ function InnerReady({
|
||||
|
||||
const header = <MessagesListHeader convo={convo} />
|
||||
|
||||
let footer: React.ReactNode = null
|
||||
if (isDisabled) {
|
||||
footer = <ChatDisabled />
|
||||
} else if (convo && primaryMember && primaryMemberModeration?.blocked) {
|
||||
footer = (
|
||||
<MessagesListBlockedFooter
|
||||
recipient={primaryMember}
|
||||
convoId={convo.view.id}
|
||||
hasMessages={hasMessages}
|
||||
moderation={primaryMemberModeration}
|
||||
/>
|
||||
)
|
||||
} else if (convo?.kind === 'group') {
|
||||
if (convo.details.lockStatus === 'locked') {
|
||||
footer = <ChatLocked convo={convo} />
|
||||
} else if (convo.details.lockStatus === 'locked-permanently') {
|
||||
footer = <ChatEnded convo={convo} />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{IS_LIQUID_GLASS ? (
|
||||
@@ -280,18 +302,7 @@ function InnerReady({
|
||||
setHasScrolled={setHasScrolled}
|
||||
hasAcceptOverride={!!params.accept}
|
||||
transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0}
|
||||
footer={
|
||||
isDisabled ? (
|
||||
<ChatDisabled />
|
||||
) : convo && primaryMember && primaryMemberModeration?.blocked ? (
|
||||
<MessagesListBlockedFooter
|
||||
recipient={primaryMember}
|
||||
convoId={convo.view.id}
|
||||
hasMessages={hasMessages}
|
||||
moderation={primaryMemberModeration}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
footer={footer}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import {Pressable} from 'react-native'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {StackActions, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {logger} from '#/logger'
|
||||
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {type ConvoWithDetails} from '#/components/dms/util'
|
||||
import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {LeaveChatPrompt} from '../ConversationSettings/prompts'
|
||||
import {ChatFooter} from './ChatFooter'
|
||||
|
||||
export function ChatEnded({
|
||||
convo,
|
||||
}: {
|
||||
convo: Extract<ConvoWithDetails, {kind: 'group'}>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
|
||||
const leaveChatPrompt = Prompt.usePromptControl()
|
||||
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const primaryMember = convo?.primaryMember
|
||||
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
|
||||
|
||||
const {mutate: leaveConvo} = useLeaveConvo(convo.view.id, {
|
||||
onSuccess: () => {
|
||||
// Settings > Chat > Chat list
|
||||
navigation.dispatch(StackActions.pop(2))
|
||||
},
|
||||
onError: e => {
|
||||
logger.error('Failed to leave group chat', {message: e})
|
||||
Toast.show(l({message: 'Failed to leave group chat', context: 'toast'}), {
|
||||
type: 'error',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<ChatFooter heading={l`This chat has ended`} icon={CircleXIcon}>
|
||||
{isOwner ? null : (
|
||||
<>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
hitSlop={HITSLOP_10}
|
||||
style={[a.mx_md]}
|
||||
onPress={leaveChatPrompt.open}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_semi_bold,
|
||||
a.leading_snug,
|
||||
{
|
||||
color: t.palette.negative_500,
|
||||
},
|
||||
]}>
|
||||
<Trans>Leave chat</Trans>
|
||||
</Text>
|
||||
</Pressable>
|
||||
<LeaveChatPrompt
|
||||
control={leaveChatPrompt}
|
||||
groupName={convo.details.name}
|
||||
onConfirm={leaveConvo}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ChatFooter>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {type Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function ChatFooter({
|
||||
children,
|
||||
heading,
|
||||
subheading,
|
||||
icon: Icon,
|
||||
}: React.PropsWithChildren<{
|
||||
heading: string
|
||||
subheading?: string
|
||||
icon: React.ComponentType<SVGIconProps>
|
||||
}>) {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<View style={[a.p_lg]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.p_md,
|
||||
a.rounded_full,
|
||||
t.atoms.bg_contrast_50,
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
{
|
||||
minHeight: 32,
|
||||
},
|
||||
]}>
|
||||
<Icon
|
||||
size="md"
|
||||
fill={t.atoms.text_contrast_medium.color}
|
||||
style={[a.mr_sm]}
|
||||
/>
|
||||
<View>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_semi_bold,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{heading}
|
||||
</Text>
|
||||
{subheading ? (
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.text_xs,
|
||||
a.leading_snug,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{subheading}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import {Pressable} from 'react-native'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {StackActions, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {logger} from '#/logger'
|
||||
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
|
||||
import {useLockConvo} from '#/state/queries/messages/lock-conversation'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {type ConvoWithDetails} from '#/components/dms/util'
|
||||
import {Lock_Stroke2_Corner0_Rounded as LockIcon} from '#/components/icons/Lock'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {LeaveChatPrompt} from '../ConversationSettings/prompts'
|
||||
import {ChatFooter} from './ChatFooter'
|
||||
|
||||
export function ChatLocked({
|
||||
convo,
|
||||
}: {
|
||||
convo: Extract<ConvoWithDetails, {kind: 'group'}>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
|
||||
const leaveChatPrompt = Prompt.usePromptControl()
|
||||
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const primaryMember = convo?.primaryMember
|
||||
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
|
||||
|
||||
const {mutate: lockConvo} = useLockConvo(convo.view.id, {
|
||||
onSuccess: () => {
|
||||
Toast.show(l({message: 'Group chat unlocked', context: 'toast'}))
|
||||
},
|
||||
onError: e => {
|
||||
logger.error('Failed to unlock group chat', {message: e})
|
||||
Toast.show(l`Failed to unlock group chat`, {type: 'error'})
|
||||
},
|
||||
})
|
||||
|
||||
const {mutate: leaveConvo} = useLeaveConvo(convo.view.id, {
|
||||
onSuccess: () => {
|
||||
// Settings > Chat > Chat list
|
||||
navigation.dispatch(StackActions.pop(2))
|
||||
},
|
||||
onError: e => {
|
||||
logger.error('Failed to leave group chat', {message: e})
|
||||
Toast.show(l({message: 'Failed to leave group chat', context: 'toast'}), {
|
||||
type: 'error',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<ChatFooter
|
||||
heading={l`This chat is locked`}
|
||||
subheading={l`No one can send messages`}
|
||||
icon={LockIcon}>
|
||||
{isOwner ? (
|
||||
<>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
hitSlop={HITSLOP_10}
|
||||
style={[a.mx_md]}
|
||||
onPress={() => lockConvo({lock: false})}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_semi_bold,
|
||||
a.leading_snug,
|
||||
{
|
||||
color: t.palette.negative_500,
|
||||
},
|
||||
]}>
|
||||
<Trans>Unlock chat</Trans>
|
||||
</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
hitSlop={HITSLOP_10}
|
||||
style={[a.mx_md]}
|
||||
onPress={leaveChatPrompt.open}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_semi_bold,
|
||||
a.leading_snug,
|
||||
{
|
||||
color: t.palette.negative_500,
|
||||
},
|
||||
]}>
|
||||
<Trans>Leave chat</Trans>
|
||||
</Text>
|
||||
</Pressable>
|
||||
<LeaveChatPrompt
|
||||
control={leaveChatPrompt}
|
||||
groupName={convo.details.name}
|
||||
onConfirm={leaveConvo}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ChatFooter>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user