[Chat] Enforce limits for group size (#10753)
This commit is contained in:
@@ -667,7 +667,6 @@ function SearchInput({
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
// @ts-ignore bottom sheet input types issue — esb
|
||||
ref={inputRef}
|
||||
placeholder={l`Search`}
|
||||
value={value}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} can’t 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} can’t be added</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</ProfileCard.Header>
|
||||
</View>
|
||||
</ProfileCard.Header>
|
||||
</View>
|
||||
{enabled ? <Toggle.Checkbox /> : null}
|
||||
{enabled ? <Toggle.Checkbox /> : null}
|
||||
</>
|
||||
)}
|
||||
</Toggle.Item>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user