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