diff --git a/src/components/dms/getSystemMessageInfo.ts b/src/components/dms/getSystemMessageInfo.ts index c8eb54764a..fe9525d4b2 100644 --- a/src/components/dms/getSystemMessageInfo.ts +++ b/src/components/dms/getSystemMessageInfo.ts @@ -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, diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index 450a7104e0..0146d3b8c9 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -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 = + let footer: React.ReactNode = null + if (isDisabled) { + footer = + } else if (convo && primaryMember && primaryMemberModeration?.blocked) { + footer = ( + + ) + } else if (convo?.kind === 'group') { + if (convo.details.lockStatus === 'locked') { + footer = + } else if (convo.details.lockStatus === 'locked-permanently') { + footer = + } + } + return ( <> {IS_LIQUID_GLASS ? ( @@ -280,18 +302,7 @@ function InnerReady({ setHasScrolled={setHasScrolled} hasAcceptOverride={!!params.accept} transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0} - footer={ - isDisabled ? ( - - ) : convo && primaryMember && primaryMemberModeration?.blocked ? ( - - ) : null - } + footer={footer} /> )} diff --git a/src/screens/Messages/components/ChatEnded.tsx b/src/screens/Messages/components/ChatEnded.tsx new file mode 100644 index 0000000000..1ee13b68d3 --- /dev/null +++ b/src/screens/Messages/components/ChatEnded.tsx @@ -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 +}) { + const t = useTheme() + const {t: l} = useLingui() + + const leaveChatPrompt = Prompt.usePromptControl() + + const navigation = useNavigation() + 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 ( + + {isOwner ? null : ( + <> + + + Leave chat + + + + + )} + + ) +} diff --git a/src/screens/Messages/components/ChatFooter.tsx b/src/screens/Messages/components/ChatFooter.tsx new file mode 100644 index 0000000000..97c5e53310 --- /dev/null +++ b/src/screens/Messages/components/ChatFooter.tsx @@ -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 +}>) { + const t = useTheme() + + return ( + + + + + + + {heading} + + {subheading ? ( + + {subheading} + + ) : null} + + + {children} + + + ) +} diff --git a/src/screens/Messages/components/ChatLocked.tsx b/src/screens/Messages/components/ChatLocked.tsx new file mode 100644 index 0000000000..7e0d9ebafd --- /dev/null +++ b/src/screens/Messages/components/ChatLocked.tsx @@ -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 +}) { + const t = useTheme() + const {t: l} = useLingui() + + const leaveChatPrompt = Prompt.usePromptControl() + + const navigation = useNavigation() + 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 ( + + {isOwner ? ( + <> + lockConvo({lock: false})}> + + Unlock chat + + + + ) : ( + <> + + + Leave chat + + + + + )} + + ) +}