Enable access for group clip clops with no messages (#10276)

This commit is contained in:
DS Boyce
2026-04-17 10:40:37 -07:00
committed by GitHub
parent 226a321a27
commit 9e9ff70682
2 changed files with 157 additions and 110 deletions
+141 -92
View File
@@ -13,7 +13,11 @@ 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 {logger} from '#/logger'
import {type Shadow} from '#/state/cache/profile-shadow' import {type Shadow} from '#/state/cache/profile-shadow'
import {isConvoActive, useConvo} from '#/state/messages/convo' import {
type ActiveConvoStates,
isConvoActive,
useConvo,
} from '#/state/messages/convo'
import {type ConvoItem} from '#/state/messages/convo/types' 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'
@@ -36,10 +40,13 @@ export function MessagesListHeader({
moderation, moderation,
}: { }: {
profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation?: ModerationDecision moderation?: ModerationDecision | null
}) { }) {
const t = useTheme() const t = useTheme()
const convoState = useConvo()
const isGroupChat = convoState?.isGroup?.()
const blockInfo = useMemo(() => { const blockInfo = useMemo(() => {
if (!moderation) return if (!moderation) return
const modui = moderation.ui('profileView') const modui = moderation.ui('profileView')
@@ -58,12 +65,21 @@ 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>
{profile && moderation && blockInfo ? ( {isConvoActive(convoState) ? (
<HeaderReady moderation && blockInfo && profile && !isGroupChat ? (
profile={profile} <ProfileHeaderReady
moderation={moderation} convoState={convoState}
blockInfo={blockInfo} profile={profile}
/> moderation={moderation}
blockInfo={blockInfo}
/>
) : (
<GroupHeaderReady
convoState={convoState}
profile={profile}
moderation={moderation}
/>
)
) : ( ) : (
<> <>
<View style={[a.flex_row, a.align_center, a.gap_md, a.flex_1]}> <View style={[a.flex_row, a.align_center, a.gap_md, a.flex_1]}>
@@ -94,11 +110,13 @@ export function MessagesListHeader({
) )
} }
function HeaderReady({ function ProfileHeaderReady({
convoState,
profile, profile,
moderation, moderation,
blockInfo, blockInfo,
}: { }: {
convoState: ActiveConvoStates
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation: ModerationDecision moderation: ModerationDecision
blockInfo: { blockInfo: {
@@ -107,21 +125,12 @@ function HeaderReady({
} }
}) { }) {
const {t: l} = useLingui() const {t: l} = useLingui()
const t = useTheme()
const convoState = useConvo()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const navigation = useNavigation<NavigationProp>()
const groupInfo = convoState.getGroupInfo?.()
const isGroupChat = groupInfo != null
const isDeletedAccount = profile?.handle === 'missing.invalid' const isDeletedAccount = profile?.handle === 'missing.invalid'
const displayName = isGroupChat const displayName = isDeletedAccount
? (groupInfo.name ?? l`${profile.handle}'s group chat`) ? l`Deleted Account`
: isDeletedAccount : createSanitizedDisplayName(profile, true, moderation.ui('displayName'))
? l`Deleted Account`
: createSanitizedDisplayName(profile, true, moderation.ui('displayName'))
const latestMessageFromOther = convoState.items.findLast( const latestMessageFromOther = convoState.items.findLast(
(item: ConvoItem) => (item: ConvoItem) =>
@@ -134,6 +143,63 @@ function HeaderReady({
? latestMessageFromOther.message ? latestMessageFromOther.message
: undefined : undefined
return (
<Wrapper
heading={
<Link
label={l`View ${displayName}s profile`}
style={[a.flex_row, a.gap_md, a.flex_1, a.pr_md]}
to={makeProfileLink(profile)}>
<PreviewableUserAvatar
size={PFP_SIZE}
profile={profile}
moderation={moderation.ui('avatar')}
disableHoverCard={moderation.blocked}
/>
<ProfileBadges profile={profile} size="md" style={[a.pl_xs]} />
</Link>
}
muted={convoState.convo?.muted}
settings={
isConvoActive(convoState) ? (
<ConvoMenu
convo={convoState.convo}
profile={profile}
currentScreen="conversation"
blockInfo={blockInfo}
latestReportableMessage={latestReportableMessage}
/>
) : null
}
/>
)
}
function GroupHeaderReady({
convoState,
profile,
moderation,
}: {
convoState: ActiveConvoStates
profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed>
moderation?: ModerationDecision | null
}) {
const {t: l} = useLingui()
const navigation = useNavigation<NavigationProp>()
const groupInfo = convoState.getGroupInfo?.()
const isDeletedAccount = profile?.handle === 'missing.invalid'
const displayName = isDeletedAccount
? l`Deleted Account`
: profile
? createSanitizedDisplayName(profile, true, moderation?.ui('displayName'))
: undefined
const groupName =
groupInfo?.name ??
(displayName ? l`${displayName}s group chat` : l`Group chat`)
const handleNavigateToSettings = () => { const handleNavigateToSettings = () => {
const convoId = convoState.convo?.id const convoId = convoState.convo?.id
if (convoId) { if (convoId) {
@@ -145,84 +211,67 @@ function HeaderReady({
} }
} }
return (
<Wrapper
heading={
<>
<AvatarBubbles size="small" profiles={convoState.recipients ?? []} />
<Text style={[a.text_md, a.font_semi_bold]} numberOfLines={1}>
{groupName}
</Text>
</>
}
muted={convoState.convo?.muted}
settings={
isConvoActive(convoState) ? (
<Button
label={l`Open group chat settings`}
size="small"
color="secondary"
shape="round"
variant="ghost"
style={[a.bg_transparent]}
onPress={handleNavigateToSettings}>
<ButtonIcon icon={DotsHorizontalIcon} size="md" />
</Button>
) : null
}
/>
)
}
function Wrapper({
heading,
muted,
settings,
}: {
heading: React.ReactNode
muted: boolean
settings: React.ReactNode
}) {
return ( return (
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
<View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}> <View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}>
{isGroupChat ? ( <View style={[a.flex_row, a.align_center, a.gap_md, a.flex_1, a.pr_md]}>
<View {heading}
style={[a.flex_row, a.align_center, a.gap_md, a.flex_1, a.pr_md]}> <MuteStatus muted={muted} />
<AvatarBubbles </View>
size="small"
profiles={convoState.recipients ?? []}
/>
<Text style={[a.text_md, a.font_semi_bold]} numberOfLines={1}>
{displayName}
</Text>
</View>
) : (
<Link
label={l`View ${displayName}'s profile`}
style={[a.flex_row, a.gap_md, a.flex_1, a.pr_md]}
to={makeProfileLink(profile)}>
<PreviewableUserAvatar
size={PFP_SIZE}
profile={profile}
moderation={moderation.ui('avatar')}
disableHoverCard={moderation.blocked}
/>
<View style={[a.flex_1]}>
<View style={[a.flex_row, a.align_center]}>
<Text
emoji
style={[a.text_md, a.font_semi_bold, a.self_start]}
numberOfLines={1}>
{displayName}
</Text>
<ProfileBadges profile={profile} size="md" style={[a.pl_xs]} />
{convoState.convo?.muted && (
<>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
{' '}
&middot;{' '}
</Text>
<BellOffIcon
size="sm"
style={t.atoms.text_contrast_medium}
/>
</>
)}
</View>
</View>
</Link>
)}
<View style={[{minHeight: PFP_SIZE}, a.justify_center]}> <View style={[{minHeight: PFP_SIZE}, a.justify_center]}>
<Layout.Header.Slot> <Layout.Header.Slot>{settings}</Layout.Header.Slot>
{isConvoActive(convoState) ? (
isGroupChat ? (
<Button
label={l`Open group chat settings`}
size="small"
color="secondary"
shape="round"
variant="ghost"
style={[a.bg_transparent]}
onPress={handleNavigateToSettings}>
<ButtonIcon icon={DotsHorizontalIcon} size="md" />
</Button>
) : (
<ConvoMenu
convo={convoState.convo}
profile={profile}
currentScreen="conversation"
blockInfo={blockInfo}
latestReportableMessage={latestReportableMessage}
/>
)
) : null}
</Layout.Header.Slot>
</View> </View>
</View> </View>
</View> </View>
) )
} }
function MuteStatus({muted}: {muted: boolean}) {
const t = useTheme()
return muted ? (
<>
<Text style={[a.text_md, t.atoms.text_contrast_medium]}> &middot; </Text>
<BellOffIcon size="sm" style={t.atoms.text_contrast_medium} />
</>
) : undefined
}
+16 -18
View File
@@ -173,16 +173,12 @@ function Inner() {
</View> </View>
)} )}
<View style={[a.flex_1]}> <View style={[a.flex_1]}>
{moderation && recipient ? ( <InnerReady
<InnerReady moderation={moderation}
moderation={moderation} recipient={recipient}
recipient={recipient} hasScrolled={hasScrolled}
hasScrolled={hasScrolled} setHasScrolled={setHasScrolled}
setHasScrolled={setHasScrolled} />
/>
) : (
<View style={[a.align_center, a.gap_sm, a.flex_1]} />
)}
{!readyToShow && ( {!readyToShow && (
<View <View
style={[ style={[
@@ -210,8 +206,8 @@ function InnerReady({
hasScrolled, hasScrolled,
setHasScrolled, setHasScrolled,
}: { }: {
moderation: ModerationDecision moderation: ModerationDecision | null
recipient: Shadow<AppBskyActorDefs.ProfileViewDetailed> recipient: Shadow<AppBskyActorDefs.ProfileViewDetailed> | undefined
hasScrolled: boolean hasScrolled: boolean
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>> setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
}) { }) {
@@ -289,12 +285,14 @@ function InnerReady({
hasAcceptOverride={!!params.accept} hasAcceptOverride={!!params.accept}
transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0} transparentHeaderHeight={IS_LIQUID_GLASS ? headerHeight : 0}
footer={ footer={
<MessagesListBlockedFooter moderation && recipient ? (
recipient={recipient} <MessagesListBlockedFooter
convoId={convoState.convo.id} recipient={recipient}
hasMessages={convoState.items.length > 0} convoId={convoState.convo.id}
moderation={moderation} hasMessages={convoState.items.length > 0}
/> moderation={moderation}
/>
) : null
} }
/> />
)} )}