[Chat] Enforce limits for group size (#10753)

This commit is contained in:
Samuel Newman
2026-06-05 23:17:29 +03:00
committed by GitHub
parent 2e9bfd363c
commit b236f274c3
6 changed files with 109 additions and 32 deletions
+11
View File
@@ -11,6 +11,7 @@ import {Trans, useLingui} from '@lingui/react/macro'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {useChatActorStatusQuery} from '#/state/queries/messages/get-status'
import {useListConvoMembersQuery} from '#/state/queries/messages/list-convo-members'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useSession} from '#/state/session'
@@ -141,6 +142,15 @@ export function AddMembersFlow({
[memberListData],
)
const {data: chatStatus} = useChatActorStatusQuery()
const groupMemberLimit = chatStatus?.groupMemberLimit
// The existing members (including the viewer) already occupy slots, so the
// number of people that can still be added is whatever's left.
const remainingSlots =
groupMemberLimit !== undefined
? Math.max(0, groupMemberLimit - memberListData.length)
: undefined
const [{groupChatDids, groupChatProfiles}, dispatch] = useReducer(reducer, {
groupChatDids: [],
groupChatProfiles: [],
@@ -471,6 +481,7 @@ export function AddMembersFlow({
values={groupChatDids}
onChange={setGroupChatMembers}
type="checkbox"
maxSelections={remainingSlots}
label={l`Add group chat members`}
style={web([a.contents])}>
<Dialog.InnerFlatList
+32 -2
View File
@@ -10,8 +10,10 @@ import {LayoutAnimation, type TextInput, View} from 'react-native'
import {moderateProfile, type ModerationOpts} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {MAX_GROUP_NAME_GRAPHEME_LENGTH} from '#/lib/constants'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {useChatActorStatusQuery} from '#/state/queries/messages/get-status'
@@ -212,6 +214,7 @@ export function InitiateChatFlow({
const {data: chatStatus} = useChatActorStatusQuery()
const canCreateGroups = chatStatus?.canCreateGroups ?? true
const groupMemberLimit = chatStatus?.groupMemberLimit
const [searchText, setSearchText] = useState('')
@@ -458,6 +461,11 @@ export function InitiateChatFlow({
}
}, [])
const groupNameTooLong = isOverMaxGraphemeCount({
text: groupName,
maxCount: MAX_GROUP_NAME_GRAPHEME_LENGTH,
})
let buttonLabel = l`Continue to group name`
let buttonText = l`Next`
let handleButtonPress = handlePressNext
@@ -470,7 +478,7 @@ export function InitiateChatFlow({
buttonText = l`Create`
handleButtonPress = handlePressConfirm
showButton = true
isButtonDisabled = groupName === ''
isButtonDisabled = groupName === '' || groupNameTooLong
break
}
@@ -564,7 +572,7 @@ export function InitiateChatFlow({
{chatState === ChatState.GROUP_NAME ? (
<View
style={[a.w_full, a.relative, web(a.pt_md), native(a.pt_xl)]}>
<TextField.Root>
<TextField.Root isInvalid={groupNameTooLong}>
<TextField.Input
label={l`Group name`}
value={groupName}
@@ -573,6 +581,7 @@ export function InitiateChatFlow({
selectTextOnFocus={IS_NATIVE}
autoFocus={false}
accessibilityRole="text"
clearButtonMode="while-editing"
autoCorrect={false}
autoComplete="off"
autoCapitalize="none"
@@ -582,6 +591,20 @@ export function InitiateChatFlow({
}
/>
</TextField.Root>
{groupNameTooLong ? (
<Text
style={[
a.text_sm,
a.mt_xs,
a.font_semi_bold,
{color: t.palette.negative_400},
]}>
<Trans>
Group name is too long. The maximum number of characters
is {MAX_GROUP_NAME_GRAPHEME_LENGTH}.
</Trans>
</Text>
) : null}
</View>
) : (
<UserSearchInput
@@ -622,6 +645,8 @@ export function InitiateChatFlow({
handleButtonPress,
buttonText,
groupName,
groupNameTooLong,
t.palette.negative_400,
searchText,
control,
showChatProfileTabs,
@@ -664,6 +689,11 @@ export function InitiateChatFlow({
values={groupChatDids}
onChange={setGroupChatMembers}
type="checkbox"
maxSelections={
// groupMemberLimit counts the creator, who is added implicitly, so
// reserve one slot for them
groupMemberLimit ? groupMemberLimit - 1 : undefined
}
label={
chatState === ChatState.NEW_GROUP_CHAT
? l`Select group chat members`
@@ -34,32 +34,40 @@ export function GroupChatProfileCard({
name={profile.did}
label={displayName}
style={[a.flex_1, a.py_sm, a.px_lg]}>
<View style={[a.flex_grow, !enabled ? {opacity: 0.5} : null]}>
<ProfileCard.Header>
<ProfileCard.Avatar
profile={profile}
moderationOpts={moderationOpts}
size={44}
disabledPreview
/>
<View>
<ProfileCard.Name
profile={profile}
moderationOpts={moderationOpts}
/>
{enabled ? (
<ProfileCard.Handle profile={profile} />
) : (
<Text
style={[a.leading_snug, t.atoms.text_contrast_high]}
numberOfLines={2}>
<Trans>{handle} cant be added</Trans>
</Text>
)}
{({disabled, selected}) => (
<>
<View
style={[
a.flex_grow,
!enabled || (disabled && !selected) ? {opacity: 0.5} : null,
]}>
<ProfileCard.Header>
<ProfileCard.Avatar
profile={profile}
moderationOpts={moderationOpts}
size={44}
disabledPreview
/>
<View>
<ProfileCard.Name
profile={profile}
moderationOpts={moderationOpts}
/>
{enabled ? (
<ProfileCard.Handle profile={profile} />
) : (
<Text
style={[a.leading_snug, t.atoms.text_contrast_high]}
numberOfLines={2}>
<Trans>{handle} cant be added</Trans>
</Text>
)}
</View>
</ProfileCard.Header>
</View>
</ProfileCard.Header>
</View>
{enabled ? <Toggle.Checkbox /> : null}
{enabled ? <Toggle.Checkbox /> : null}
</>
)}
</Toggle.Item>
)
}