Use convo view instead of convo state for driving the UI (#10290)

This commit is contained in:
DS Boyce
2026-04-17 15:23:44 -07:00
committed by GitHub
parent feebc6a98b
commit 935347c73d
3 changed files with 127 additions and 120 deletions
+39 -57
View File
@@ -2,6 +2,7 @@ import {useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import { import {
type AppBskyActorDefs, type AppBskyActorDefs,
ChatBskyConvoDefs,
type ModerationCause, type ModerationCause,
type ModerationDecision, type ModerationDecision,
} from '@atproto/api' } from '@atproto/api'
@@ -11,14 +12,7 @@ import {useNavigation} from '@react-navigation/native'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {logger} from '#/logger'
import {type Shadow} from '#/state/cache/profile-shadow' import {type Shadow} from '#/state/cache/profile-shadow'
import {
type ActiveConvoStates,
isConvoActive,
useConvo,
} from '#/state/messages/convo'
import {type ConvoItem} from '#/state/messages/convo/types'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
@@ -32,20 +26,22 @@ import {Link} from '#/components/Link'
import {ProfileBadges} from '#/components/ProfileBadges' import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_LIQUID_GLASS, IS_WEB} from '#/env' import {IS_LIQUID_GLASS, IS_WEB} from '#/env'
import {type ConvoWithDetails} from './util'
const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE
export function MessagesListHeader({ export function MessagesListHeader({
convo,
profile, profile,
moderation, moderation,
}: { }: {
convo?: ConvoWithDetails | null
profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation?: ModerationDecision | null moderation?: ModerationDecision | null
}) { }) {
const t = useTheme() const t = useTheme()
const convoState = useConvo() const isGroupChat = convo?.kind === 'group'
const isGroupChat = convoState?.isGroup?.()
const blockInfo = useMemo(() => { const blockInfo = useMemo(() => {
if (!moderation) return if (!moderation) return
@@ -65,17 +61,17 @@ export function MessagesListHeader({
<View style={[{minHeight: PFP_SIZE}, a.justify_center]}> <View style={[{minHeight: PFP_SIZE}, a.justify_center]}>
<Layout.Header.BackButton /> <Layout.Header.BackButton />
</View> </View>
{isConvoActive(convoState) ? ( {convo ? (
moderation && blockInfo && profile && !isGroupChat ? ( moderation && blockInfo && profile && !isGroupChat ? (
<ProfileHeaderReady <ProfileHeaderReady
convoState={convoState} convo={convo}
profile={profile} profile={profile}
moderation={moderation} moderation={moderation}
blockInfo={blockInfo} blockInfo={blockInfo}
/> />
) : ( ) : (
<GroupHeaderReady <GroupHeaderReady
convoState={convoState} convo={convo}
profile={profile} profile={profile}
moderation={moderation} moderation={moderation}
/> />
@@ -111,12 +107,12 @@ export function MessagesListHeader({
} }
function ProfileHeaderReady({ function ProfileHeaderReady({
convoState, convo,
profile, profile,
moderation, moderation,
blockInfo, blockInfo,
}: { }: {
convoState: ActiveConvoStates convo: ConvoWithDetails
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation: ModerationDecision moderation: ModerationDecision
blockInfo: { blockInfo: {
@@ -132,15 +128,10 @@ function ProfileHeaderReady({
? l`Deleted Account` ? l`Deleted Account`
: createSanitizedDisplayName(profile, true, moderation.ui('displayName')) : createSanitizedDisplayName(profile, true, moderation.ui('displayName'))
const latestMessageFromOther = convoState.items.findLast(
(item: ConvoItem) =>
item.type === 'message' &&
item.message.sender.did !== currentAccount?.did,
)
const latestReportableMessage = const latestReportableMessage =
latestMessageFromOther?.type === 'message' ChatBskyConvoDefs.isMessageView(convo.view.lastMessage) &&
? latestMessageFromOther.message convo.view.lastMessage.sender?.did !== currentAccount?.did
? convo.view.lastMessage
: undefined : undefined
return ( return (
@@ -164,28 +155,26 @@ function ProfileHeaderReady({
</View> </View>
</Link> </Link>
} }
muted={convoState.convo?.muted} muted={convo.view.muted}
settings={ settings={
isConvoActive(convoState) ? ( <ConvoMenu
<ConvoMenu convo={convo.view}
convo={convoState.convo} profile={profile}
profile={profile} currentScreen="conversation"
currentScreen="conversation" blockInfo={blockInfo}
blockInfo={blockInfo} latestReportableMessage={latestReportableMessage}
latestReportableMessage={latestReportableMessage} />
/>
) : null
} }
/> />
) )
} }
function GroupHeaderReady({ function GroupHeaderReady({
convoState, convo,
profile, profile,
moderation, moderation,
}: { }: {
convoState: ActiveConvoStates convo: ConvoWithDetails
profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation?: ModerationDecision | null moderation?: ModerationDecision | null
}) { }) {
@@ -193,7 +182,7 @@ function GroupHeaderReady({
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const groupInfo = convoState.getGroupInfo?.() const groupInfo = convo.kind === 'group' ? convo.details : undefined
const isDeletedAccount = profile?.handle === 'missing.invalid' const isDeletedAccount = profile?.handle === 'missing.invalid'
const displayName = isDeletedAccount const displayName = isDeletedAccount
@@ -206,40 +195,33 @@ function GroupHeaderReady({
(displayName ? l`${displayName}s group chat` : l`Group chat`) (displayName ? l`${displayName}s group chat` : l`Group chat`)
const handleNavigateToSettings = () => { const handleNavigateToSettings = () => {
const convoId = convoState.convo?.id navigation.navigate('MessagesConversationSettings', {
if (convoId) { conversation: convo.view.id,
navigation.navigate('MessagesConversationSettings', { })
conversation: convoId,
})
} else {
logger.error(`handleNavigateToSettings: missing convo ID`)
}
} }
return ( return (
<Wrapper <Wrapper
heading={ heading={
<> <>
<AvatarBubbles size="small" profiles={convoState.recipients ?? []} /> <AvatarBubbles size="small" profiles={convo.members} />
<Text style={[a.text_md, a.font_semi_bold]} numberOfLines={1}> <Text style={[a.text_md, a.font_semi_bold]} numberOfLines={1}>
{groupName} {groupName}
</Text> </Text>
</> </>
} }
muted={convoState.convo?.muted} muted={convo.view.muted}
settings={ settings={
isConvoActive(convoState) ? ( <Button
<Button label={l`Open group chat settings`}
label={l`Open group chat settings`} size="small"
size="small" color="secondary"
color="secondary" shape="round"
shape="round" variant="ghost"
variant="ghost" style={[a.bg_transparent]}
style={[a.bg_transparent]} onPress={handleNavigateToSettings}>
onPress={handleNavigateToSettings}> <ButtonIcon icon={DotsHorizontalIcon} size="md" />
<ButtonIcon icon={DotsHorizontalIcon} size="md" /> </Button>
</Button>
) : null
} }
/> />
) )
+37 -10
View File
@@ -35,6 +35,7 @@ import {ConvoStatus} from '#/state/messages/convo/types'
import {useCurrentConvoId} from '#/state/messages/current-convo-id' import {useCurrentConvoId} from '#/state/messages/current-convo-id'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {MessagesList} from '#/screens/Messages/components/MessagesList' import {MessagesList} from '#/screens/Messages/components/MessagesList'
import {atoms as a, useTheme, web} from '#/alf' import {atoms as a, useTheme, web} from '#/alf'
@@ -46,6 +47,7 @@ import {
} from '#/components/dialogs/EmailDialog' } from '#/components/dialogs/EmailDialog'
import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter' import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter'
import {MessagesListHeader} from '#/components/dms/MessagesListHeader' import {MessagesListHeader} from '#/components/dms/MessagesListHeader'
import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util'
import {Error} from '#/components/Error' import {Error} from '#/components/Error'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
@@ -104,9 +106,14 @@ function Inner() {
const t = useTheme() const t = useTheme()
const convoState = useConvo() const convoState = useConvo()
const {_} = useLingui() const {_} = useLingui()
const {currentAccount} = useSession()
const isFocused = useIsFocused() const isFocused = useIsFocused()
const {top: topInset} = useSafeAreaInsets() const {top: topInset} = useSafeAreaInsets()
const convo = convoState.convo
? parseConvoView(convoState.convo, currentAccount?.did)
: null
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const {data: recipientUnshadowed} = useProfileQuery({ const {data: recipientUnshadowed} = useProfileQuery({
did: convoState.getPrimaryMember?.()?.did, did: convoState.getPrimaryMember?.()?.did,
@@ -144,9 +151,13 @@ function Inner() {
<Layout.Center <Layout.Center
style={[a.w_full, IS_LIQUID_GLASS && {paddingTop: topInset}]}> style={[a.w_full, IS_LIQUID_GLASS && {paddingTop: topInset}]}>
{moderation ? ( {moderation ? (
<MessagesListHeader profile={recipient} moderation={moderation} /> <MessagesListHeader
convo={convo}
profile={recipient}
moderation={moderation}
/>
) : ( ) : (
<MessagesListHeader /> <MessagesListHeader convo={convo} />
)} )}
</Layout.Center> </Layout.Center>
<Error <Error
@@ -166,9 +177,13 @@ function Inner() {
{!readyToShow && ( {!readyToShow && (
<View style={IS_LIQUID_GLASS && {paddingTop: topInset}}> <View style={IS_LIQUID_GLASS && {paddingTop: topInset}}>
{moderation ? ( {moderation ? (
<MessagesListHeader profile={recipient} moderation={moderation} /> <MessagesListHeader
convo={convo}
profile={recipient}
moderation={moderation}
/>
) : ( ) : (
<MessagesListHeader /> <MessagesListHeader convo={convo} />
)} )}
</View> </View>
)} )}
@@ -178,6 +193,9 @@ function Inner() {
recipient={recipient} recipient={recipient}
hasScrolled={hasScrolled} hasScrolled={hasScrolled}
setHasScrolled={setHasScrolled} setHasScrolled={setHasScrolled}
convo={convo}
isActive={isConvoActive(convoState)}
hasMessages={isConvoActive(convoState) && convoState.items.length > 0}
/> />
{!readyToShow && ( {!readyToShow && (
<View <View
@@ -205,13 +223,18 @@ function InnerReady({
recipient, recipient,
hasScrolled, hasScrolled,
setHasScrolled, setHasScrolled,
convo,
isActive,
hasMessages,
}: { }: {
moderation: ModerationDecision | null moderation: ModerationDecision | null
recipient: Shadow<AppBskyActorDefs.ProfileViewDetailed> | undefined recipient: Shadow<AppBskyActorDefs.ProfileViewDetailed> | undefined
hasScrolled: boolean hasScrolled: boolean
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>> setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
convo: ConvoWithDetails | null
isActive: boolean
hasMessages: boolean
}) { }) {
const convoState = useConvo()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {top: topInset} = useSafeAreaInsets() const {top: topInset} = useSafeAreaInsets()
const [headerHeight, setHeaderHeight] = useState(0) const [headerHeight, setHeaderHeight] = useState(0)
@@ -262,7 +285,11 @@ function InnerReady({
}, [maybeBlockForEmailVerification]) }, [maybeBlockForEmailVerification])
const header = ( const header = (
<MessagesListHeader profile={recipient} moderation={moderation} /> <MessagesListHeader
convo={convo}
profile={recipient}
moderation={moderation}
/>
) )
return ( return (
@@ -277,7 +304,7 @@ function InnerReady({
) : ( ) : (
header header
)} )}
{isConvoActive(convoState) && ( {isActive && (
<MessagesList <MessagesList
hasScrolled={hasScrolled} hasScrolled={hasScrolled}
setHasScrolled={setHasScrolled} setHasScrolled={setHasScrolled}
@@ -285,11 +312,11 @@ function InnerReady({
hasAcceptOverride={!!params.accept} hasAcceptOverride={!!params.accept}
transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0} transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0}
footer={ footer={
moderation && recipient ? ( moderation && recipient && convo ? (
<MessagesListBlockedFooter <MessagesListBlockedFooter
recipient={recipient} recipient={recipient}
convoId={convoState.convo.id} convoId={convo.view.id}
hasMessages={convoState.items.length > 0} hasMessages={hasMessages}
moderation={moderation} moderation={moderation}
/> />
) : null ) : null
+51 -53
View File
@@ -1,6 +1,6 @@
import {useMemo, useState} from 'react' import {useMemo, useState} from 'react'
import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native'
import {type ChatBskyConvoDefs, moderateProfile} from '@atproto/api' import {moderateProfile} from '@atproto/api'
import {plural} from '@lingui/core/macro' import {plural} from '@lingui/core/macro'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {StackActions, useNavigation} from '@react-navigation/native' import {StackActions, useNavigation} from '@react-navigation/native'
@@ -34,6 +34,7 @@ import {AvatarBubbles} from '#/components/AvatarBubbles'
import {Button, type ButtonColor, ButtonIcon} from '#/components/Button' import {Button, type ButtonColor, ButtonIcon} from '#/components/Button'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
import {AddMembersFlow} from '#/components/dms/AddMembersFlow' import {AddMembersFlow} from '#/components/dms/AddMembersFlow'
import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util'
import {Error} from '#/components/Error' import {Error} from '#/components/Error'
import * as TextField from '#/components/forms/TextField' import * as TextField from '#/components/forms/TextField'
import {useInteractionState} from '#/components/hooks/useInteractionState' import {useInteractionState} from '#/components/hooks/useInteractionState'
@@ -126,9 +127,14 @@ function SettingsInner() {
const convoState = useConvo() const convoState = useConvo()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const primaryMember = convoState?.getPrimaryMember?.()
const data: bsky.profile.AnyProfileView[] = convoState.convo?.members ?? [] const convo = convoState.convo
? parseConvoView(convoState.convo, currentAccount?.did)
: null
const primaryMember = convo?.primaryMember
const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did
const data: bsky.profile.AnyProfileView[] = convo?.members ?? []
const invites: string[] = [] const invites: string[] = []
const items = [ const items = [
@@ -163,11 +169,23 @@ function SettingsInner() {
function renderItem({item}: {item: Item}) { function renderItem({item}: {item: Item}) {
switch (item.type) { switch (item.type) {
case 'MEMBERS_AND_REQUESTS': case 'MEMBERS_AND_REQUESTS':
return <MembersAndRequests memberCount={data.length} requestCount={5} /> return (
<MembersAndRequests
memberCount={data.length}
requestCount={5}
isOwner={isOwner}
/>
)
case 'ADD_MEMBERS_LINK': case 'ADD_MEMBERS_LINK':
return <AddMembersLink /> return <AddMembersLink isOwner={isOwner} />
case 'CHAT_MEMBER': case 'CHAT_MEMBER':
return <Member profile={item.profile} status={item.status} /> return (
<Member
profile={item.profile}
status={item.status}
isOwner={isOwner}
/>
)
default: default:
return null return null
} }
@@ -194,8 +212,8 @@ function SettingsInner() {
initialNumToRender={initialNumToRender} initialNumToRender={initialNumToRender}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
ListHeaderComponent={ ListHeaderComponent={
convoState.convo ? ( convo ? (
<SettingsHeader convo={convoState.convo} profiles={data} /> <SettingsHeader convo={convo} isOwner={isOwner} />
) : ( ) : (
<SettingsHeaderPlaceholder /> <SettingsHeaderPlaceholder />
) )
@@ -211,21 +229,15 @@ function SettingsInner() {
function MembersAndRequests({ function MembersAndRequests({
memberCount, memberCount,
requestCount, requestCount,
isOwner,
}: { }: {
memberCount: number memberCount: number
requestCount: number requestCount: number
isOwner: boolean
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const convoState = useConvo()
const {currentAccount} = useSession()
const isOwner =
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
return ( return (
<View style={[a.flex_row, a.justify_between, a.mx_xl, a.mt_lg, a.mb_sm]}> <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]}> <View style={[a.flex_row, a.align_center]}>
@@ -254,20 +266,12 @@ function MembersAndRequests({
) )
} }
function AddMembersLink() { function AddMembersLink({isOwner}: {isOwner: boolean}) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const convoState = useConvo()
const {currentAccount} = useSession()
const addMembersControl = Dialog.useDialogControl() const addMembersControl = Dialog.useDialogControl()
const isOwner =
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
if (!isOwner) { if (!isOwner) {
return null return null
} }
@@ -354,9 +358,11 @@ function AddMembersLink() {
function Member({ function Member({
profile, profile,
status, status,
isOwner,
}: { }: {
profile: Shadow<bsky.profile.AnyProfileView> profile: Shadow<bsky.profile.AnyProfileView>
status: 'owner' | 'member' | 'invited' status: 'owner' | 'member' | 'invited'
isOwner: boolean
}) { }) {
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const t = useTheme() const t = useTheme()
@@ -388,7 +394,9 @@ function Member({
break break
} }
} else { } else {
statusBadge = <MemberMenu profile={profile} type={status} /> statusBadge = (
<MemberMenu profile={profile} type={status} isOwner={isOwner} />
)
} }
return ( return (
@@ -496,9 +504,11 @@ function StatusButton({
function MemberMenu({ function MemberMenu({
profile, profile,
type, type,
isOwner,
}: { }: {
profile: Shadow<bsky.profile.AnyProfileView> profile: Shadow<bsky.profile.AnyProfileView>
type: 'owner' | 'member' | 'invited' type: 'owner' | 'member' | 'invited'
isOwner: boolean
}) { }) {
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const t = useTheme() const t = useTheme()
@@ -506,16 +516,9 @@ function MemberMenu({
const ax = useAnalytics() const ax = useAnalytics()
const requireEmailVerification = useRequireEmailVerification() const requireEmailVerification = useRequireEmailVerification()
const convoState = useConvo()
const {currentAccount} = useSession()
const blockMemberPrompt = Prompt.usePromptControl() const blockMemberPrompt = Prompt.usePromptControl()
const isOwner =
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
const {data: convoAvailability} = useGetConvoAvailabilityQuery(profile.did) const {data: convoAvailability} = useGetConvoAvailabilityQuery(profile.did)
const {mutate: initiateConvo} = useGetConvoForMembers({ const {mutate: initiateConvo} = useGetConvoForMembers({
onSuccess: ({convo}) => { onSuccess: ({convo}) => {
@@ -706,29 +709,22 @@ function MemberMenu({
function SettingsHeader({ function SettingsHeader({
convo, convo,
profiles, isOwner,
}: { }: {
convo: ChatBskyConvoDefs.ConvoView convo: ConvoWithDetails
profiles: bsky.profile.AnyProfileView[] isOwner: boolean
}) { }) {
const t = useTheme() const t = useTheme()
const {t: l} = useLingui() const {t: l} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const convoState = useConvo()
const {currentAccount} = useSession()
const groupName = convoState.getGroupInfo?.()?.name ?? '' const groupName = convo.kind === 'group' ? convo.details.name : ''
const [newGroupName, setNewGroupName] = useState(groupName) const [newGroupName, setNewGroupName] = useState(groupName)
const [isLocked, setIsLocked] = useState(false) const [isLocked, setIsLocked] = useState(false)
const isOwner = const {mutate: editGroupName} = useEditGroupName(convo.view.id, {
currentAccount?.did == null
? false
: convoState.getPrimaryMember?.()?.did === currentAccount.did
const {mutate: editGroupName} = useEditGroupName(convo.id, {
onError: e => { onError: e => {
setNewGroupName(groupName) setNewGroupName(groupName)
logger.error('Failed to edit group chat name', {message: e}) logger.error('Failed to edit group chat name', {message: e})
@@ -738,7 +734,7 @@ function SettingsHeader({
}, },
}) })
const {mutate: muteConvo} = useMuteConvo(convo.id, { const {mutate: muteConvo} = useMuteConvo(convo.view.id, {
onSuccess: data => { onSuccess: data => {
if (data.convo.muted) { if (data.convo.muted) {
Toast.show(l({message: 'Group chat muted', context: 'toast'})) Toast.show(l({message: 'Group chat muted', context: 'toast'}))
@@ -754,7 +750,7 @@ function SettingsHeader({
}, },
}) })
const {mutate: leaveConvo} = useLeaveConvo(convo.id, { const {mutate: leaveConvo} = useLeaveConvo(convo.view.id, {
onMutate: () => { onMutate: () => {
navigation.dispatch(StackActions.pop(2)) navigation.dispatch(StackActions.pop(2))
}, },
@@ -772,7 +768,7 @@ function SettingsHeader({
const leaveChatPrompt = Prompt.usePromptControl() const leaveChatPrompt = Prompt.usePromptControl()
const handleToggleMute = () => { const handleToggleMute = () => {
muteConvo({mute: !convo?.muted}) muteConvo({mute: !convo.view.muted})
} }
const handleLeaveChat = () => { const handleLeaveChat = () => {
@@ -815,7 +811,7 @@ function SettingsHeader({
<View <View
style={[a.px_xl, a.py_4xl, a.border_b, t.atoms.border_contrast_low]}> style={[a.px_xl, a.py_4xl, a.border_b, t.atoms.border_contrast_low]}>
<View style={[a.align_center, a.justify_center]}> <View style={[a.align_center, a.justify_center]}>
<AvatarBubbles profiles={profiles} /> <AvatarBubbles profiles={convo.members} />
</View> </View>
<Text <Text
style={[ style={[
@@ -846,12 +842,14 @@ function SettingsHeader({
a.pt_2xl, a.pt_2xl,
]}> ]}>
<SettingsButton <SettingsButton
color={convo?.muted ? 'negative_subtle' : 'secondary'} color={convo.view.muted ? 'negative_subtle' : 'secondary'}
icon={convo?.muted ? BellOffIcon : BellIcon} icon={convo.view.muted ? BellOffIcon : BellIcon}
label={ label={
convo?.muted ? l`Unmute this group chat` : l`Mute this group chat` convo.view.muted
? l`Unmute this group chat`
: l`Mute this group chat`
} }
text={convo?.muted ? l`Muted` : l`Mute`} text={convo.view.muted ? l`Muted` : l`Mute`}
onPress={handleToggleMute} onPress={handleToggleMute}
/> />
{isOwner ? ( {isOwner ? (