diff --git a/.gitignore b/.gitignore index c0adc37228..b6c32b9f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,9 @@ stats.json # VSCode .vscode +# Zed +.zed + # gitignore and github actions !.gitignore !.github diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx index 5e3cecf077..9fbff9d092 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -1,5 +1,9 @@ import {ScrollView, View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import { + type ChatBskyActorDefs, + moderateProfile, + type ModerationOpts, +} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -66,8 +70,8 @@ export function RecentChats({ if (!convo) return null if ( - (convo.kind === 'direct' && - convo.primaryMember.handle === 'missing.invalid') || + !convo.primaryMember || + convo.primaryMember.handle === 'missing.invalid' || convo.view.muted ) { return null @@ -77,6 +81,7 @@ export function RecentChats({ onSelectChat(convo.view.id)} moderationOpts={moderationOpts} /> @@ -103,15 +108,17 @@ function RecentChatItem({ onPress, moderationOpts, convo, + primaryMember, }: { onPress: () => void moderationOpts: ModerationOpts convo: ConvoWithDetails + primaryMember: ChatBskyActorDefs.ProfileViewBasic }) { const {_} = useLingui() const t = useTheme() - const primaryProfile = useProfileShadow(convo.primaryMember) + const primaryProfile = useProfileShadow(primaryMember) const moderation = moderateProfile(primaryProfile, moderationOpts) const name = diff --git a/src/components/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx index e5675a8339..31e48cc799 100644 --- a/src/components/dialogs/SearchablePeopleList.tsx +++ b/src/components/dialogs/SearchablePeopleList.tsx @@ -479,14 +479,15 @@ function ExistingChatCard({ const {t: l} = useLingui() const enabled = convo.kind === 'group' ? convo.details.lockStatus === 'unlocked' : true - const moderation = moderateProfile(convo.primaryMember, moderationOpts) const name = convo.kind === 'group' ? convo.details.name : createSanitizedDisplayName( convo.primaryMember, true, - moderation.ui('displayName'), + moderateProfile(convo.primaryMember, moderationOpts).ui( + 'displayName', + ), ) const handleOnPress = useCallback(() => { diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts index 5140ba64ca..490db31c6b 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -69,7 +69,7 @@ export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( | { kind: 'group' details: $Typed - primaryMember: GroupConvoMember // the owner + primaryMember?: GroupConvoMember // the owner - may have left, thus optional members: Array } | { @@ -117,11 +117,6 @@ export function parseConvoView( } } - if (!owner) { - logger.warn('No owner found in group convo') - return null - } - return { view: convoView, kind: 'group', diff --git a/src/screens/Messages/ConversationSettings/Member.tsx b/src/screens/Messages/ConversationSettings/Member.tsx index 828bec8eaf..e11bfa9d4e 100644 --- a/src/screens/Messages/ConversationSettings/Member.tsx +++ b/src/screens/Messages/ConversationSettings/Member.tsx @@ -47,7 +47,7 @@ export function Member({ const displayName = isDeletedAccount ? l`Deleted Account` : createSanitizedDisplayName(profile, true, moderation.ui('displayName')) - const isProfileOwner = profile.did === convo.primaryMember.did + const isProfileOwner = profile.did === convo.primaryMember?.did const isSelf = currentAccount?.did === profile.did let statusBadge: React.ReactNode | null = null if (isSelf) { diff --git a/src/screens/Messages/ConversationSettings/index.tsx b/src/screens/Messages/ConversationSettings/index.tsx index c0be0b1864..29df1f5ba0 100644 --- a/src/screens/Messages/ConversationSettings/index.tsx +++ b/src/screens/Messages/ConversationSettings/index.tsx @@ -1,6 +1,10 @@ import {useState} from 'react' import {View} from 'react-native' -import {ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' +import { + ChatBskyActorDefs, + ChatBskyConvoDefs, + ModerationOpts, +} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -14,6 +18,7 @@ import { import {logger} from '#/logger' import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' import {ConvoStatus} from '#/state/messages/convo/types' +import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useEditGroupChatName} from '#/state/queries/messages/edit-group-chat-name' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {useListConvoMembersQuery} from '#/state/queries/messages/list-convo-members' @@ -99,6 +104,7 @@ function SettingsInner() { const {t: l} = useLingui() const convoState = useConvo() const navigation = useNavigation() + const moderationOpts = useModerationOpts() if (convoState.status === ConvoStatus.Error) { return ( @@ -111,7 +117,7 @@ function SettingsInner() { ) } - if (!isConvoActive(convoState)) { + if (!isConvoActive(convoState) || !moderationOpts) { return ( @@ -135,7 +141,9 @@ function SettingsInner() { ) } - return + return ( + + ) } function keyExtractor(item: Item) { @@ -157,8 +165,10 @@ function isGroupMember( function GroupSettings({ convo, + moderationOpts, }: { convo: Extract + moderationOpts: ModerationOpts }) { const initialNumToRender = useInitialNumToRender({minItemHeight: 68}) const bottomBarOffset = useBottomBarOffset() @@ -166,7 +176,7 @@ function GroupSettings({ const {currentAccount} = useSession() const primaryMember = convo.primaryMember - const isOwner = primaryMember.did === currentAccount?.did + const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did const {data: memberListData = [], isPending} = useListConvoMembersQuery({ convoId: convo.view.id, @@ -209,8 +219,8 @@ function GroupSettings({ ...memberListData .filter(isGroupMember) .sort((a, b) => { - const aIsOwner = a.did === primaryMember.did - const bIsOwner = b.did === primaryMember.did + 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 @@ -223,7 +233,7 @@ function GroupSettings({ key: profile.did, profile, status: - primaryMember.did === profile.did + primaryMember?.did === profile.did ? 'owner' : invites.includes(profile.did) ? 'invited' @@ -271,7 +281,13 @@ function GroupSettings({ desktopFixedHeight initialNumToRender={initialNumToRender} keyExtractor={keyExtractor} - ListHeaderComponent={} + ListHeaderComponent={ + + } renderItem={renderItem} sideBorders={false} windowSize={11} @@ -282,9 +298,11 @@ function GroupSettings({ function SettingsHeader({ convo, isOwner, + moderationOpts, }: { convo: Extract isOwner: boolean + moderationOpts: ModerationOpts }) { const t = useTheme() const {i18n, t: l} = useLingui() @@ -517,11 +535,15 @@ function SettingsHeader({ onChangeText={setNewGroupName} onConfirm={handleEditName} /> - + {convo.primaryMember && ( + + )} moderateProfile(groupOwner, moderationOpts), + () => + groupOwner ? moderateProfile(groupOwner, moderationOpts) : undefined, [groupOwner, moderationOpts], ) @@ -215,8 +220,8 @@ function BaseChatItem({ accessibilityHint: string isDeletedAccount: boolean isBlockedAccount: boolean - primaryProfile: Shadow - primaryProfileModeration: ModerationDecision + primaryProfile?: Shadow + primaryProfileModeration?: ModerationDecision showMenu?: boolean showProfileBadges: boolean postAlerts?: React.ReactNode @@ -236,6 +241,7 @@ function BaseChatItem({ const hasUnread = convo.unreadCount > 0 && !isDeletedAccount const blockInfo = useMemo(() => { + if (!primaryProfileModeration) return {listBlocks: [], userBlock: undefined} const modui = primaryProfileModeration.ui('profileView') const blocks = modui.alerts.filter(alert => alert.type === 'blocking') const listBlocks = blocks.filter(alert => alert.source.type === 'list') @@ -455,7 +461,7 @@ function BaseChatItem({ - {showProfileBadges && ( + {showProfileBadges && primaryProfile && ( - {showMenu && ( + {/* TODO: Allow showing menu for groups where the owner has left! */} + {showMenu && primaryProfile && ( control: Dialog.DialogOuterProps['control'] + owner: GroupConvoMember isOwner: boolean + moderationOpts: ModerationOpts }) { const t = useTheme() const {t: l, i18n} = useLingui() - const ownerName = createSanitizedDisplayName(convo.primaryMember) + const ownerName = createSanitizedDisplayName( + owner, + false, + moderateProfile(owner, moderationOpts).ui('displayName'), + ) const {joinLink} = convo.details const enabledStatus = joinLink?.enabledStatus diff --git a/src/screens/Messages/components/MessagesListInfoPanel.tsx b/src/screens/Messages/components/MessagesListInfoPanel.tsx index 1b1621cac7..344c1dc81e 100644 --- a/src/screens/Messages/components/MessagesListInfoPanel.tsx +++ b/src/screens/Messages/components/MessagesListInfoPanel.tsx @@ -2,7 +2,9 @@ import {View} from 'react-native' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {logger} from '#/logger' +import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useAddGroupMembers} from '#/state/queries/messages/add-group-members' +import {useListConvoMembersQuery} from '#/state/queries/messages/list-convo-members' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {AvatarBubbles} from '#/components/AvatarBubbles' @@ -23,13 +25,16 @@ export function MessagesListInfoPanel({ }) { const t = useTheme() const {t: l} = useLingui() + const moderationOpts = useModerationOpts() + const convoId = convo.view.id const addMembersControl = Dialog.useDialogControl() const inviteLinkControl = Dialog.useDialogControl() const {currentAccount} = useSession() - const convoId = convo.view.id + const {data: fullMemberList} = useListConvoMembersQuery({convoId}) + const {mutate: addGroupMembers} = useAddGroupMembers(convoId, { onSuccess: () => { addMembersControl.close() @@ -46,9 +51,9 @@ export function MessagesListInfoPanel({ // (isOwner && groupConvo) || // (!isOwner && groupConvo && joinLink?.enabledStatus === 'enabled') - const isOwner = convo?.primaryMember.did === currentAccount?.did + const isOwner = convo.primaryMember?.did === currentAccount?.did - const members = (convo?.members ?? []).filter( + const members = (convo.members ?? []).filter( profile => profile.did !== currentAccount?.did, ) @@ -81,7 +86,7 @@ export function MessagesListInfoPanel({ return ( <> - + {convo.details.name ? ( {convo.details.name} @@ -139,11 +144,15 @@ export function MessagesListInfoPanel({ ) : null} - + {convo.primaryMember && moderationOpts && ( + + )} - - - + {convo.primaryMember && ( + + + + )} {/* spacer, since you can't nest pressables */} {/* Placeholder text so that it responds to the font height */} @@ -60,7 +63,7 @@ export function RequestListItem({ paddingLeft: tokens.space.lg + 52 + tokens.space.md, }, ]}> - {!isDeletedAccount ? ( + {convo.primaryMember && !isDeletedAccount ? ( <> (() => { + console.log('accepted', accepted) + console.log('request', request) const acceptedCount = calculateCount( accepted, currentAccount?.did, @@ -453,19 +456,18 @@ function calculateCount( return ( convos .filter(convo => convo.id !== currentConvoId) - .reduce((acc, convo) => { - const otherMember = convo.members.find( - member => member.did !== currentAccountDid, - ) + .reduce((acc, convoView) => { + const convo = parseConvoView(convoView, currentAccountDid) - if (!otherMember || !moderationOpts) return acc + if (!convo || !moderationOpts) return acc - const moderation = moderateProfile(otherMember, moderationOpts) const shouldIgnore = - convo.muted || - moderation.blocked || - otherMember.handle === 'missing.invalid' - const unreadCount = !shouldIgnore && convo.unreadCount > 0 ? 1 : 0 + convo.view.muted || + !convo.primaryMember || + moderateProfile(convo.primaryMember, moderationOpts).blocked || + convo.primaryMember.handle === 'missing.invalid' || + (convo.kind === 'group' && convo.details.lockStatus !== 'unlocked') + const unreadCount = !shouldIgnore && convo.view.unreadCount > 0 ? 1 : 0 return acc + unreadCount }, 0) ?? 0