Add Info Panel to direct chats (#10576)
This commit is contained in:
@@ -65,6 +65,7 @@ import {ChatStatusInfo} from './ChatStatusInfo'
|
||||
import {groupSystemMessages, type RenderItem} from './groupSystemMessages'
|
||||
import {InviteLinkDialogProvider} from './InviteLinkDialogProvider'
|
||||
import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed'
|
||||
import {MessagesListGroupInfoPanel} from './MessagesListGroupInfoPanel'
|
||||
import {MessagesListInfoPanel} from './MessagesListInfoPanel'
|
||||
import {KeyboardStickyView} from './vendor/KeyboardStickyView'
|
||||
|
||||
@@ -509,9 +510,12 @@ export function MessagesList({
|
||||
ListHeaderComponent={
|
||||
<>
|
||||
<MaybeLoader isLoading={convoState.isFetchingHistory} />
|
||||
{convoState.convo?.kind === 'group' &&
|
||||
convoState.hasAllHistory ? (
|
||||
<MessagesListInfoPanel convo={convoState.convo} />
|
||||
{convoState.hasAllHistory ? (
|
||||
convoState.convo?.kind === 'group' ? (
|
||||
<MessagesListGroupInfoPanel convo={convoState.convo} />
|
||||
) : (
|
||||
<MessagesListInfoPanel convo={convoState.convo} />
|
||||
)
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import {View} from 'react-native'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {logger} from '#/logger'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAddGroupMembers} from '#/state/queries/messages/add-group-members'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {AvatarBubbles} from '#/components/AvatarBubbles'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {AddMembersFlow} from '#/components/dms/AddMembersFlow'
|
||||
import {type ConvoWithDetails} from '#/components/dms/util'
|
||||
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
|
||||
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {InviteLinkDialog} from './InviteLinkDialog'
|
||||
|
||||
export function MessagesListGroupInfoPanel({
|
||||
convo,
|
||||
}: {
|
||||
convo: Extract<ConvoWithDetails, {kind: 'group'}>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const convoId = convo.view.id
|
||||
|
||||
const addMembersControl = Dialog.useDialogControl()
|
||||
const inviteLinkControl = Dialog.useDialogControl()
|
||||
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const {mutate: addGroupMembers} = useAddGroupMembers(convoId, {
|
||||
onSuccess: () => {
|
||||
addMembersControl.close()
|
||||
},
|
||||
onError: e => {
|
||||
logger.error('Failed to add group chat members', {message: e})
|
||||
Toast.show(l`Failed to add members`, {type: 'error'})
|
||||
},
|
||||
})
|
||||
|
||||
// TODO Enable this once the feature is working end-to-end. -dsb
|
||||
// const joinLink = groupConvo?.details.joinLink
|
||||
const isJoinLinkEnabled = false
|
||||
// (isOwner && groupConvo) ||
|
||||
// (!isOwner && groupConvo && joinLink?.enabledStatus === 'enabled')
|
||||
|
||||
const isOwner = convo.primaryMember?.did === currentAccount?.did
|
||||
|
||||
const members = (convo.members ?? []).filter(
|
||||
profile => profile.did !== currentAccount?.did,
|
||||
)
|
||||
|
||||
let names: React.ReactNode = null
|
||||
if (members.length === 1) {
|
||||
names = (
|
||||
<Trans>New chat with {createSanitizedDisplayName(members[0])}</Trans>
|
||||
)
|
||||
} else if (members.length === 2) {
|
||||
names = (
|
||||
<Trans>
|
||||
New chat with {createSanitizedDisplayName(members[0])} and{' '}
|
||||
{createSanitizedDisplayName(members[1])}
|
||||
</Trans>
|
||||
)
|
||||
} else if (members.length > 2) {
|
||||
const memberCount = convo.details.memberCount - 2
|
||||
names = (
|
||||
<Trans>
|
||||
New chat with {createSanitizedDisplayName(members[0])},{' '}
|
||||
{createSanitizedDisplayName(members[1])}, and{' '}
|
||||
<Plural
|
||||
value={memberCount}
|
||||
one={`${memberCount} more`}
|
||||
other={`${memberCount} more`}
|
||||
/>
|
||||
.
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
|
||||
const showButtons = isOwner || isJoinLinkEnabled
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={[a.align_center, a.justify_center]}>
|
||||
<AvatarBubbles animate={true} profiles={convo.members} />
|
||||
{convo.details.name ? (
|
||||
<Text
|
||||
style={[a.text_2xl, a.font_bold, a.mt_lg, a.px_lg, t.atoms.text]}>
|
||||
{convo.details.name}
|
||||
</Text>
|
||||
) : null}
|
||||
{names ? (
|
||||
<Text
|
||||
style={[
|
||||
a.px_lg,
|
||||
a.mt_xs,
|
||||
a.text_center,
|
||||
a.text_sm,
|
||||
t.atoms.text_contrast_high,
|
||||
showButtons ? null : a.mb_4xl,
|
||||
]}>
|
||||
{names}
|
||||
</Text>
|
||||
) : null}
|
||||
{showButtons ? (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.gap_sm,
|
||||
a.mt_lg,
|
||||
a.mb_4xl,
|
||||
]}>
|
||||
{isOwner ? (
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
label={l`Click here to add people to this group chat`}
|
||||
onPress={() => addMembersControl.open()}>
|
||||
<ButtonIcon icon={PersonPlusIcon} />
|
||||
<ButtonText>
|
||||
<Trans>Add people</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
) : null}
|
||||
{isJoinLinkEnabled ? (
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
label={
|
||||
isOwner
|
||||
? l`Click here to create or manage an invite link for this group chat`
|
||||
: l`Click here to view the invite link for this group chat`
|
||||
}
|
||||
onPress={inviteLinkControl.open}>
|
||||
<ButtonIcon icon={ChainLinkIcon} />
|
||||
<ButtonText>
|
||||
<Trans>Invite link</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
{convo.primaryMember && moderationOpts && (
|
||||
<InviteLinkDialog
|
||||
convo={convo}
|
||||
owner={convo.primaryMember}
|
||||
moderationOpts={moderationOpts}
|
||||
isOwner={isOwner}
|
||||
control={inviteLinkControl}
|
||||
/>
|
||||
)}
|
||||
<Dialog.Outer
|
||||
control={addMembersControl}
|
||||
testID="addChatMembersDialog"
|
||||
nativeOptions={{fullHeight: true}}>
|
||||
<Dialog.Handle />
|
||||
<AddMembersFlow
|
||||
convo={convo}
|
||||
title={l`Add people`}
|
||||
onAddMembers={(members, profiles) =>
|
||||
addGroupMembers({members, profiles})
|
||||
}
|
||||
/>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,176 +1,99 @@
|
||||
import {View} from 'react-native'
|
||||
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||
import {moderateProfile} from '@atproto/api'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {logger} from '#/logger'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAddGroupMembers} from '#/state/queries/messages/add-group-members'
|
||||
import {useSession} from '#/state/session'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {AvatarBubbles} from '#/components/AvatarBubbles'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {AddMembersFlow} from '#/components/dms/AddMembersFlow'
|
||||
import {type ConvoWithDetails} from '#/components/dms/util'
|
||||
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
|
||||
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Person_Stroke2_Corner2_Rounded as PersonIcon} from '#/components/icons/Person'
|
||||
import {ProfileBadges} from '#/components/ProfileBadges'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {InviteLinkDialog} from './InviteLinkDialog'
|
||||
|
||||
export function MessagesListInfoPanel({
|
||||
convo,
|
||||
}: {
|
||||
convo: Extract<ConvoWithDetails, {kind: 'group'}>
|
||||
convo: Extract<ConvoWithDetails, {kind: 'direct'}>
|
||||
}) {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const convoId = convo.view.id
|
||||
|
||||
const addMembersControl = Dialog.useDialogControl()
|
||||
const inviteLinkControl = Dialog.useDialogControl()
|
||||
|
||||
const {currentAccount} = useSession()
|
||||
const moderationOpts = useModerationOpts()
|
||||
|
||||
const {mutate: addGroupMembers} = useAddGroupMembers(convoId, {
|
||||
onSuccess: () => {
|
||||
addMembersControl.close()
|
||||
},
|
||||
onError: e => {
|
||||
logger.error('Failed to add group chat members', {message: e})
|
||||
Toast.show(l`Failed to add members`, {type: 'error'})
|
||||
},
|
||||
})
|
||||
|
||||
// TODO Enable this once the feature is working end-to-end. -dsb
|
||||
// const joinLink = groupConvo?.details.joinLink
|
||||
const isJoinLinkEnabled = false
|
||||
// (isOwner && groupConvo) ||
|
||||
// (!isOwner && groupConvo && joinLink?.enabledStatus === 'enabled')
|
||||
|
||||
const isOwner = convo.primaryMember?.did === currentAccount?.did
|
||||
|
||||
const members = (convo.members ?? []).filter(
|
||||
const profile = convo.members.filter(
|
||||
profile => profile.did !== currentAccount?.did,
|
||||
)
|
||||
|
||||
let names: React.ReactNode = null
|
||||
if (members.length === 1) {
|
||||
names = (
|
||||
<Trans>New chat with {createSanitizedDisplayName(members[0])}</Trans>
|
||||
)
|
||||
} else if (members.length === 2) {
|
||||
names = (
|
||||
<Trans>
|
||||
New chat with {createSanitizedDisplayName(members[0])} and{' '}
|
||||
{createSanitizedDisplayName(members[1])}
|
||||
</Trans>
|
||||
)
|
||||
} else if (members.length > 2) {
|
||||
const memberCount = convo.details.memberCount - 2
|
||||
names = (
|
||||
<Trans>
|
||||
New chat with {createSanitizedDisplayName(members[0])},{' '}
|
||||
{createSanitizedDisplayName(members[1])}, and{' '}
|
||||
<Plural
|
||||
value={memberCount}
|
||||
one={`${memberCount} more`}
|
||||
other={`${memberCount} more`}
|
||||
/>
|
||||
.
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
|
||||
const showButtons = isOwner || isJoinLinkEnabled
|
||||
)[0]
|
||||
const handle = sanitizeHandle(profile.handle, '@')
|
||||
const displayName = moderationOpts
|
||||
? createSanitizedDisplayName(
|
||||
profile,
|
||||
true,
|
||||
moderateProfile(profile, moderationOpts).ui('displayName'),
|
||||
)
|
||||
: handle
|
||||
const profileLink =
|
||||
profile.handle && !isInvalidHandle(profile.handle)
|
||||
? profile.handle
|
||||
: profile.did
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={[a.align_center, a.justify_center]}>
|
||||
<AvatarBubbles animate={true} profiles={convo.members} />
|
||||
{convo.details.name ? (
|
||||
<Text
|
||||
style={[a.text_2xl, a.font_bold, a.mt_lg, a.px_lg, t.atoms.text]}>
|
||||
{convo.details.name}
|
||||
</Text>
|
||||
) : null}
|
||||
{names ? (
|
||||
<Text
|
||||
style={[
|
||||
a.px_lg,
|
||||
a.mt_xs,
|
||||
a.text_center,
|
||||
a.text_sm,
|
||||
t.atoms.text_contrast_high,
|
||||
showButtons ? null : a.mb_4xl,
|
||||
]}>
|
||||
{names}
|
||||
</Text>
|
||||
) : null}
|
||||
{showButtons ? (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.gap_sm,
|
||||
a.mt_lg,
|
||||
a.mb_4xl,
|
||||
]}>
|
||||
{isOwner ? (
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
label={l`Click here to add people to this group chat`}
|
||||
onPress={() => addMembersControl.open()}>
|
||||
<ButtonIcon icon={PersonPlusIcon} />
|
||||
<ButtonText>
|
||||
<Trans>Add people</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
) : null}
|
||||
{isJoinLinkEnabled ? (
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
label={
|
||||
isOwner
|
||||
? l`Click here to create or manage an invite link for this group chat`
|
||||
: l`Click here to view the invite link for this group chat`
|
||||
}
|
||||
onPress={inviteLinkControl.open}>
|
||||
<ButtonIcon icon={ChainLinkIcon} />
|
||||
<ButtonText>
|
||||
<Trans>Invite link</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
<View style={[a.align_center, a.justify_center]}>
|
||||
<UserAvatar type="user" size={88} avatar={profile?.avatar} />
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.gap_xs,
|
||||
a.mt_lg,
|
||||
]}>
|
||||
<Text style={[a.text_2xl, a.font_bold, t.atoms.text]}>
|
||||
{displayName}
|
||||
</Text>
|
||||
<ProfileBadges profile={profile} size="lg" />
|
||||
</View>
|
||||
{convo.primaryMember && moderationOpts && (
|
||||
<InviteLinkDialog
|
||||
convo={convo}
|
||||
owner={convo.primaryMember}
|
||||
moderationOpts={moderationOpts}
|
||||
isOwner={isOwner}
|
||||
control={inviteLinkControl}
|
||||
/>
|
||||
)}
|
||||
<Dialog.Outer
|
||||
control={addMembersControl}
|
||||
testID="addChatMembersDialog"
|
||||
nativeOptions={{fullHeight: true}}>
|
||||
<Dialog.Handle />
|
||||
<AddMembersFlow
|
||||
convo={convo}
|
||||
title={l`Add people`}
|
||||
onAddMembers={(members, profiles) =>
|
||||
addGroupMembers({members, profiles})
|
||||
}
|
||||
/>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
<Text style={[a.text_sm, a.mt_xs, t.atoms.text_contrast_high]}>
|
||||
{handle}
|
||||
</Text>
|
||||
{moderationOpts ? (
|
||||
<View style={[a.mt_xs]}>
|
||||
<ProfileCard.Labels
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
a.gap_sm,
|
||||
a.mt_lg,
|
||||
a.mb_4xl,
|
||||
]}>
|
||||
<Button
|
||||
color="secondary"
|
||||
size="small"
|
||||
label={l`Go to user’s profile`}
|
||||
onPress={() => {
|
||||
navigation.navigate('Profile', {name: profileLink})
|
||||
}}>
|
||||
<ButtonIcon icon={PersonIcon} />
|
||||
<ButtonText>
|
||||
<Trans>Go to profile</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user