Add flow for adding people to a group clip clop (#10255)

This commit is contained in:
DS Boyce
2026-04-16 11:55:19 -07:00
committed by GitHub
parent ac68cfe98c
commit 6e3c9c3a9f
9 changed files with 849 additions and 262 deletions
+101 -57
View File
@@ -31,7 +31,8 @@ import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {AvatarBubbles} from '#/components/AvatarBubbles'
import {Button, type ButtonColor, ButtonIcon} from '#/components/Button'
import type * as Dialog from '#/components/Dialog'
import * as Dialog from '#/components/Dialog'
import {AddMembersFlow} from '#/components/dms/AddMembersFlow'
import {Error} from '#/components/Error'
import * as TextField from '#/components/forms/TextField'
import {useInteractionState} from '#/components/hooks/useInteractionState'
@@ -138,11 +139,11 @@ function SettingsInner() {
},
...[...data]
.sort((a, b) => {
const aIsAdmin = a.did === primaryMember?.did
const bIsAdmin = b.did === primaryMember?.did
const aIsOwner = a.did === primaryMember?.did
const bIsOwner = b.did === primaryMember?.did
const aIsSelf = a.did === currentAccount?.did
const bIsSelf = b.did === currentAccount?.did
if (aIsAdmin !== bIsAdmin) return aIsAdmin ? -1 : 1
if (aIsOwner !== bIsOwner) return aIsOwner ? -1 : 1
if (aIsSelf !== bIsSelf) return aIsSelf ? -1 : 1
return 0
})
@@ -216,6 +217,14 @@ function MembersAndRequests({
const t = useTheme()
const {t: l} = useLingui()
const convoState = useConvo()
const {currentAccount} = useSession()
const isOwner =
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
return (
<View style={[a.flex_row, a.justify_between, a.mx_xl, a.mt_lg, a.mb_sm]}>
<View style={[a.flex_row, a.align_center]}>
@@ -229,7 +238,7 @@ function MembersAndRequests({
{color: t.palette.contrast_500},
]}>{l`${memberCount}/${MEMBER_LIMIT}`}</Text>
</View>
{requestCount > 0 ? (
{isOwner && requestCount > 0 ? (
<InlineLinkText
label={l`View incoming group chat requests`}
style={[a.text_sm, a.text_right, a.font_semi_bold]}
@@ -246,63 +255,98 @@ function MembersAndRequests({
function AddMembersLink() {
const t = useTheme()
const {t: l} = useLingui()
const convoState = useConvo()
const {currentAccount} = useSession()
const addMembersControl = Dialog.useDialogControl()
const isOwner =
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
if (!isOwner) {
return null
}
return (
<SubtleHoverWrapper>
<View
style={[
a.mx_xl,
{
marginTop: ROW_SPACING,
marginBottom: ROW_SPACING,
},
]}>
<Pressable
accessibilityRole="button"
style={({pressed}) => [
a.flex_row,
a.align_center,
a.justify_between,
pressed && web({outline: 'none'}),
<>
<SubtleHoverWrapper>
<View
style={[
a.mx_xl,
{
marginTop: ROW_SPACING,
marginBottom: ROW_SPACING,
},
]}>
{({pressed}) => (
<>
<View>
<View style={[a.flex_row, a.align_center]}>
<View
style={[
a.flex_row,
a.align_center,
a.justify_center,
a.p_lg,
a.rounded_full,
pressed
? t.atoms.bg_contrast_100
: t.atoms.bg_contrast_50,
{
height: 48,
width: 48,
},
]}>
<PlusIcon style={[t.atoms.text_contrast_high]} size="sm" />
<Pressable
accessibilityRole="button"
style={({pressed}) => [
a.flex_row,
a.align_center,
a.justify_between,
pressed && web({outline: 'none'}),
]}
onPress={() => addMembersControl.open()}>
{({pressed}) => (
<>
<View>
<View style={[a.flex_row, a.align_center]}>
<View
style={[
a.flex_row,
a.align_center,
a.justify_center,
a.p_lg,
a.rounded_full,
pressed
? t.atoms.bg_contrast_100
: t.atoms.bg_contrast_50,
{
height: 48,
width: 48,
},
]}>
<PlusIcon
style={[t.atoms.text_contrast_high]}
size="sm"
/>
</View>
<Text
style={[
a.text_md,
a.font_semi_bold,
a.pl_sm,
t.atoms.text,
]}>
<Trans>Add members</Trans>
</Text>
</View>
<Text
style={[
a.text_md,
a.font_semi_bold,
a.pl_sm,
t.atoms.text,
]}>
<Trans>Add members</Trans>
</Text>
</View>
</View>
<ChevronIcon style={[t.atoms.text_contrast_medium]} size="md" />
</>
)}
</Pressable>
</View>
</SubtleHoverWrapper>
<ChevronIcon style={[t.atoms.text_contrast_medium]} size="md" />
</>
)}
</Pressable>
</View>
</SubtleHoverWrapper>
<Dialog.Outer
control={addMembersControl}
testID="addChatMembersDialog"
nativeOptions={{fullHeight: true}}>
<Dialog.Handle />
<AddMembersFlow
title={l`Add members`}
onAddMembers={(_dids: string[]) => {
// TODO Add members here
addMembersControl.close()
}}
/>
</Dialog.Outer>
</>
)
}
@@ -6,6 +6,8 @@ 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 {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
import {Text} from '#/components/Typography'
@@ -14,8 +16,17 @@ export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
const t = useTheme()
const {t: l} = useLingui()
const addMembersControl = Dialog.useDialogControl()
const {currentAccount} = useSession()
const isOwner =
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
// TODO Get this from @api/atproto - dsb
const isLinkEnabled = false
const groupName = convoState.getGroupInfo?.()?.name
const members = (convoState?.convo?.members ?? []).filter(
@@ -47,49 +58,78 @@ export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
)
}
const showButtons = isOwner || isLinkEnabled
return (
<View style={[a.align_center, a.justify_center]}>
<AvatarBubbles animate={true} profiles={members} />
{groupName ? (
<Text style={[a.text_2xl, a.font_bold, a.mt_lg, t.atoms.text]}>
{groupName}
</Text>
) : null}
{names ? (
<Text style={[a.text_sm, a.mt_xs, t.atoms.text_contrast_high]}>
{names}
</Text>
) : 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`Click here to add people to this group chat`}
onPress={() => {}}>
<ButtonIcon icon={PersonPlusIcon} />
<ButtonText>
<Trans>Add people</Trans>
</ButtonText>
</Button>
<Button
color="secondary"
size="small"
label={l`Click here to view or create an invite link for this group chat`}
onPress={() => {}}>
<ButtonIcon icon={ChainLinkIcon} />
<ButtonText>
<Trans>Invite link</Trans>
</ButtonText>
</Button>
<>
<View style={[a.align_center, a.justify_center]}>
<AvatarBubbles animate={true} profiles={members} />
{groupName ? (
<Text style={[a.text_2xl, a.font_bold, a.mt_lg, t.atoms.text]}>
{groupName}
</Text>
) : null}
{names ? (
<Text
style={[
a.text_sm,
a.mt_xs,
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}
{isOwner || isLinkEnabled ? (
<Button
color="secondary"
size="small"
label={l`Click here to view or create an invite link for this group chat`}
onPress={() => {}}>
<ButtonIcon icon={ChainLinkIcon} />
<ButtonText>
<Trans>Invite link</Trans>
</ButtonText>
</Button>
) : null}
</View>
) : null}
</View>
</View>
<Dialog.Outer
control={addMembersControl}
testID="addChatMembersDialog"
nativeOptions={{fullHeight: true}}>
<Dialog.Handle />
<AddMembersFlow
title={l`Add people`}
onAddMembers={(_dids: string[]) => {
// TODO Add members here
addMembersControl.close()
}}
/>
</Dialog.Outer>
</>
)
}