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,
|
Unlock_Stroke2_Corner2_Rounded as UnlockIcon,
|
||||||
} from '#/components/icons/Lock'
|
} from '#/components/icons/Lock'
|
||||||
import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil'
|
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 = {
|
export type SystemMessageInfo = {
|
||||||
message: MessageDescriptor
|
message: MessageDescriptor
|
||||||
@@ -69,13 +68,7 @@ export function getSystemMessageInfo(
|
|||||||
} else if (ChatBskyConvoDefs.isSystemMessageDataUnlockConvo(data)) {
|
} else if (ChatBskyConvoDefs.isSystemMessageDataUnlockConvo(data)) {
|
||||||
return {Icon: UnlockIcon, message: msg`Chat unlocked`}
|
return {Icon: UnlockIcon, message: msg`Chat unlocked`}
|
||||||
} else if (ChatBskyConvoDefs.isSystemMessageDataLockConvoPermanently(data)) {
|
} else if (ChatBskyConvoDefs.isSystemMessageDataLockConvoPermanently(data)) {
|
||||||
const name = getReferredDisplayName(data.lockedBy, relatedProfiles)
|
return {Icon: LockIcon, message: msg`Chat ended`}
|
||||||
return {
|
|
||||||
Icon: XIcon,
|
|
||||||
message: name
|
|
||||||
? msg`${name} ended the group chat`
|
|
||||||
: msg`This group chat was ended`,
|
|
||||||
}
|
|
||||||
} else if (ChatBskyConvoDefs.isSystemMessageDataEditGroup(data)) {
|
} else if (ChatBskyConvoDefs.isSystemMessageDataEditGroup(data)) {
|
||||||
return {
|
return {
|
||||||
Icon: PencilIcon,
|
Icon: PencilIcon,
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ import {Text} from '#/components/Typography'
|
|||||||
import {useAnalytics} from '#/analytics'
|
import {useAnalytics} from '#/analytics'
|
||||||
import {IS_INTERNAL, IS_LIQUID_GLASS, IS_WEB} from '#/env'
|
import {IS_INTERNAL, IS_LIQUID_GLASS, IS_WEB} from '#/env'
|
||||||
import {ChatDisabled} from './components/ChatDisabled'
|
import {ChatDisabled} from './components/ChatDisabled'
|
||||||
|
import {ChatEnded} from './components/ChatEnded'
|
||||||
|
import {ChatLocked} from './components/ChatLocked'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
CommonNavigatorParams,
|
CommonNavigatorParams,
|
||||||
@@ -262,6 +264,26 @@ function InnerReady({
|
|||||||
|
|
||||||
const header = <MessagesListHeader convo={convo} />
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{IS_LIQUID_GLASS ? (
|
{IS_LIQUID_GLASS ? (
|
||||||
@@ -280,18 +302,7 @@ function InnerReady({
|
|||||||
setHasScrolled={setHasScrolled}
|
setHasScrolled={setHasScrolled}
|
||||||
hasAcceptOverride={!!params.accept}
|
hasAcceptOverride={!!params.accept}
|
||||||
transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0}
|
transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0}
|
||||||
footer={
|
footer={footer}
|
||||||
isDisabled ? (
|
|
||||||
<ChatDisabled />
|
|
||||||
) : convo && primaryMember && primaryMemberModeration?.blocked ? (
|
|
||||||
<MessagesListBlockedFooter
|
|
||||||
recipient={primaryMember}
|
|
||||||
convoId={convo.view.id}
|
|
||||||
hasMessages={hasMessages}
|
|
||||||
moderation={primaryMemberModeration}
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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