Improve initial loading state for chat settings (#10424)

This commit is contained in:
DS Boyce
2026-05-07 12:00:42 -07:00
committed by GitHub
parent eddbf6ba9f
commit 10cb0dbf21
3 changed files with 69 additions and 60 deletions
@@ -16,8 +16,10 @@ import {Text} from '#/components/Typography'
export function AddMembersLink({
convo,
disabled,
}: {
convo: Extract<ConvoWithDetails, {kind: 'group'}>
disabled?: boolean
}) {
const t = useTheme()
const {t: l} = useLingui()
@@ -41,7 +43,7 @@ export function AddMembersLink({
return (
<>
<Button
disabled={isAddPending}
disabled={disabled || isAddPending}
label={l`Add members`}
onPress={addMembersControl.open}>
{({interacting}) => (
@@ -23,21 +23,19 @@ export function MembersAndRequests({
return (
<View style={[a.flex_row, a.justify_between, a.px_xl, a.pt_xl, a.pb_sm]}>
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Text style={[a.text_lg, a.font_semi_bold, t.atoms.text]}>
<Trans>Members</Trans>
<Trans comment="The heading above the list of chat members.">
Members
</Trans>
</Text>
<Text style={[a.text_xs, a.font_medium, t.atoms.text_contrast_medium]}>
{l({
message: `${memberCount}/${MEMBER_LIMIT}`,
comment:
'The number of group chat members out of the total number of permitted users.',
})}
</Text>
<View
style={[a.px_xs, a.py_2xs, t.atoms.bg_contrast_50, a.rounded_full]}>
<Text
style={[a.text_xs, a.font_medium, {color: t.palette.contrast_500}]}>
{l({
message: `${memberCount}/${MEMBER_LIMIT}`,
comment:
'The number of group chat members out of the total number of permitted users.',
})}
</Text>
</View>
</View>
{isOwner && requestCount > 0 ? (
<InlineLinkText
@@ -3,7 +3,7 @@ import {View} from 'react-native'
import {
ChatBskyActorDefs,
ChatBskyConvoDefs,
ModerationOpts,
type ModerationOpts,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
@@ -117,7 +117,7 @@ function SettingsInner() {
)
}
if (!isConvoActive(convoState) || !moderationOpts) {
if (!convoState.convo || !moderationOpts) {
return (
<View style={[a.flex_1, a.align_center, a.justify_center]}>
<Loader size="xl" />
@@ -125,7 +125,7 @@ function SettingsInner() {
)
}
if (convoState.convo?.kind !== 'group') {
if (convoState.convo.kind !== 'group') {
return (
<Error
title={l`Wrong kind of conversation`}
@@ -142,7 +142,11 @@ function SettingsInner() {
}
return (
<GroupSettings convo={convoState.convo} moderationOpts={moderationOpts} />
<GroupSettings
convo={convoState.convo}
moderationOpts={moderationOpts}
isReady={isConvoActive(convoState)}
/>
)
}
@@ -166,9 +170,11 @@ function isGroupMember(
function GroupSettings({
convo,
moderationOpts,
isReady,
}: {
convo: Extract<ConvoWithDetails, {kind: 'group'}>
moderationOpts: ModerationOpts
isReady: boolean
}) {
const initialNumToRender = useInitialNumToRender({minItemHeight: 68})
const bottomBarOffset = useBottomBarOffset()
@@ -178,7 +184,7 @@ function GroupSettings({
const primaryMember = convo.primaryMember
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
const {data: memberListData = [], isPending} = useListConvoMembersQuery({
const {data: memberListData = []} = useListConvoMembersQuery({
convoId: convo.view.id,
placeholderData: convo.members,
})
@@ -197,6 +203,16 @@ function GroupSettings({
0,
) ?? 0
const groupMembers = memberListData.filter(isGroupMember).sort((a, b) => {
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 (aIsOwner !== bIsOwner) return aIsOwner ? -1 : 1
if (aIsSelf !== bIsSelf) return aIsSelf ? -1 : 1
return 0
})
const items: Item[] = [
{
type: 'MEMBERS_AND_REQUESTS',
@@ -206,41 +222,30 @@ function GroupSettings({
? [{type: 'ADD_MEMBERS_LINK', key: 'add-members-link'} as const]
: []),
]
if (isPending) {
// should never be pending if we correctly set the query cache data
items.push(
...Array.from({length: 5}, (_, i) => ({
type: 'CHAT_MEMBER_PLACEHOLDER' as const,
key: `chat-member-placeholder-${i}`,
})),
)
} else {
items.push(
...memberListData
.filter(isGroupMember)
.sort((a, b) => {
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 (aIsOwner !== bIsOwner) return aIsOwner ? -1 : 1
if (aIsSelf !== bIsSelf) return aIsSelf ? -1 : 1
return 0
})
.map(
(profile): Item => ({
type: 'CHAT_MEMBER',
key: profile.did,
profile,
status:
primaryMember?.did === profile.did
? 'owner'
: invites.includes(profile.did)
? 'invited'
: 'standard',
}),
),
)
items.push(
...groupMembers.map(
(profile): Item => ({
type: 'CHAT_MEMBER',
key: profile.did,
profile,
status:
primaryMember?.did === profile.did
? 'owner'
: invites.includes(profile.did)
? 'invited'
: 'standard',
}),
),
)
const placeholderCount = Math.max(
0,
convo.details.memberCount - groupMembers.length,
)
for (let i = 0; i < placeholderCount; i++) {
items.push({
type: 'CHAT_MEMBER_PLACEHOLDER',
key: `chat-member-placeholder-${i}`,
})
}
function renderItem({item}: {item: Item}) {
@@ -255,7 +260,7 @@ function GroupSettings({
/>
)
case 'ADD_MEMBERS_LINK':
return <AddMembersLink convo={convo} />
return <AddMembersLink convo={convo} disabled={!isReady} />
case 'CHAT_MEMBER':
return (
<Member
@@ -286,6 +291,7 @@ function GroupSettings({
convo={convo}
isOwner={isOwner}
moderationOpts={moderationOpts}
isReady={isReady}
/>
}
renderItem={renderItem}
@@ -299,10 +305,12 @@ function SettingsHeader({
convo,
isOwner,
moderationOpts,
isReady,
}: {
convo: Extract<ConvoWithDetails, {kind: 'group'}>
isOwner: boolean
moderationOpts: ModerationOpts
isReady: boolean
}) {
const t = useTheme()
const {i18n, t: l} = useLingui()
@@ -462,7 +470,7 @@ function SettingsHeader({
]}>
<SettingsButton
color={convo.view.muted ? 'negative_subtle' : 'secondary'}
disabled={isMuting}
disabled={!isReady || isMuting}
icon={convo.view.muted ? BellOffIcon : BellIcon}
label={
convo.view.muted
@@ -474,7 +482,7 @@ function SettingsHeader({
/>
{isOwner ? (
<SettingsButton
disabled={isEditingName}
disabled={!isReady || isEditingName}
icon={EditIcon}
label={l`Edit this group chats name`}
text={l`Edit name`}
@@ -483,7 +491,7 @@ function SettingsHeader({
) : null}
{isJoinLinkEnabled ? (
<SettingsButton
disabled={lockStatus !== 'unlocked'}
disabled={!isReady || lockStatus !== 'unlocked'}
icon={ChainLinkIcon}
label={
isOwner
@@ -497,7 +505,7 @@ function SettingsHeader({
{canLockGroupChat ? (
<SettingsButton
color={lockStatus === 'locked' ? 'negative_subtle' : 'secondary'}
disabled={isLocking}
disabled={!isReady || isLocking}
icon={LockIcon}
label={
lockStatus === 'locked'
@@ -512,6 +520,7 @@ function SettingsHeader({
) : null}
{!isOwner && isReportLinkEnabled && (
<SettingsButton
disabled={!isReady}
icon={FlagIcon}
label={l`Report this group chat`}
text={l`Report`}
@@ -520,7 +529,7 @@ function SettingsHeader({
)}
{!isOwner && (
<SettingsButton
disabled={isLeaving}
disabled={!isReady || isLeaving}
icon={ArrowBoxLeftIcon}
label={l`Leave this group chat`}
text={l`Leave`}