From cbe708b53be4e4a5a556aee201237402ee7ebb4f Mon Sep 17 00:00:00 2001 From: lan Date: Wed, 13 May 2026 00:31:44 +0800 Subject: [PATCH] feat(message): support group avatar in navigation and chat screen Pass group avatar through notification extras, navigation hrefs, and route parameters. Update ChatScreen to resolve group information (ID, name, and avatar) by prioritizing route parameters (e.g., from push notifications) and falling back to conversation data from the MessageManager. - Update `NotificationBootstrap` to include `groupAvatar` in query params - Update `hrefChat` to support `groupAvatar` - Refactor `useChatScreen` to use `effectiveGroupId`, `effectiveGroupName`, and `effectiveGroupAvatar` - Ensure `MessageManager` fetches conversation details if missing when activating a conversation - Update `MessageListScreen` to pass `groupAvatar` during navigation --- app/_layout.tsx | 2 + src/navigation/hrefs.ts | 4 +- src/screens/message/ChatScreen.tsx | 8 +-- src/screens/message/MessageListScreen.tsx | 1 + .../components/ChatScreen/useChatScreen.ts | 65 ++++++++++++------- src/stores/message/MessageManager.ts | 5 ++ 6 files changed, 57 insertions(+), 28 deletions(-) diff --git a/app/_layout.tsx b/app/_layout.tsx index e854b2b..6c01cda 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -161,6 +161,7 @@ function NotificationBootstrap() { const conversationType = extras.conversation_type || extras.conversationType; const groupId = extras.group_id || extras.groupId; const groupName = extras.group_name || extras.groupName; + const groupAvatar = extras.group_avatar || extras.groupAvatar; if (conversationId) { const isGroup = conversationType === 'group'; @@ -169,6 +170,7 @@ function NotificationBootstrap() { q.set('isGroupChat', '1'); if (groupId) q.set('groupId', groupId); if (groupName) q.set('groupName', groupName); + if (groupAvatar) q.set('groupAvatar', groupAvatar); } else if (senderId) { q.set('userId', senderId); } diff --git a/src/navigation/hrefs.ts b/src/navigation/hrefs.ts index c960498..dae9ef8 100644 --- a/src/navigation/hrefs.ts +++ b/src/navigation/hrefs.ts @@ -86,13 +86,15 @@ export function hrefChat(params: { isGroupChat?: boolean; groupId?: string; groupName?: string; + groupAvatar?: string; }): string { - const { conversationId, userId, isGroupChat, groupId, groupName } = params; + const { conversationId, userId, isGroupChat, groupId, groupName, groupAvatar } = params; const q = new URLSearchParams(); if (userId) q.set('userId', userId); if (isGroupChat) q.set('isGroupChat', '1'); if (groupId != null && groupId !== '') q.set('groupId', String(groupId)); if (groupName) q.set('groupName', groupName); + if (groupAvatar) q.set('groupAvatar', groupAvatar); const qs = q.toString(); return `/chat/${encodeURIComponent(conversationId)}${qs ? `?${qs}` : ''}`; } diff --git a/src/screens/message/ChatScreen.tsx b/src/screens/message/ChatScreen.tsx index a6b1cec..ca0f92d 100644 --- a/src/screens/message/ChatScreen.tsx +++ b/src/screens/message/ChatScreen.tsx @@ -165,8 +165,8 @@ export const ChatScreen: React.FC = (props) => { muteAll, followRestrictionHint, canSendPrivateImage, - routeGroupId, - routeGroupName, + effectiveGroupId, + effectiveGroupName, otherUserLastReadSeq, messageMap, loadingMore, @@ -458,7 +458,7 @@ export const ChatScreen: React.FC = (props) => { isGroupChat={isGroupChat} groupInfo={groupInfo} otherUser={otherUser} - routeGroupName={routeGroupName ?? undefined} + routeGroupName={effectiveGroupName ?? undefined} typingHint={typingHint} onBack={props.onEmbeddedBack ? props.onEmbeddedBack : () => router.back()} onTitlePress={navigateToInfo} @@ -669,7 +669,7 @@ export const ChatScreen: React.FC = (props) => { {/* 群信息侧边栏面板 - 仅大屏幕显示 */} { conversationId: String(conversation.id), groupId: String(conversation.group.id), groupName: conversation.group.name, + groupAvatar: conversation.group.avatar, isGroupChat: true, }) ); diff --git a/src/screens/message/components/ChatScreen/useChatScreen.ts b/src/screens/message/components/ChatScreen/useChatScreen.ts index 5ebbc81..3266caf 100644 --- a/src/screens/message/components/ChatScreen/useChatScreen.ts +++ b/src/screens/message/components/ChatScreen/useChatScreen.ts @@ -96,6 +96,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { isGroupChat?: string | string[]; groupId?: string | string[]; groupName?: string | string[]; + groupAvatar?: string | string[]; }>(); // 路由参数(动态段 + query);群 ID 勿用 Number(),避免超过 2^53-1 时精度丢失 // 嵌入式模式下优先使用 props,否则从路由参数获取 @@ -107,6 +108,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { isGroupChat: props.embeddedIsGroupChat ?? false, groupId: props.embeddedGroupId ?? null, groupName: props.embeddedGroupName ?? null, + groupAvatar: null as string | null, }; } const isGroupFlag = firstRouteParam(rawParams.isGroupChat); @@ -116,6 +118,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { isGroupChat: isGroupFlag === '1' || isGroupFlag === 'true', groupId: firstRouteParam(rawParams.groupId), groupName: firstRouteParam(rawParams.groupName), + groupAvatar: firstRouteParam(rawParams.groupAvatar), }; }, [ props?.embeddedConversationId, @@ -127,9 +130,10 @@ export const useChatScreen = (props?: ChatScreenProps) => { rawParams.isGroupChat, rawParams.groupId, rawParams.groupName, + rawParams.groupAvatar, ]); - const { conversationId: routeConversationId, userId: routeUserId, isGroupChat, groupId: routeGroupId, groupName: routeGroupName } = routeParams; + const { conversationId: routeConversationId, userId: routeUserId, isGroupChat, groupId: routeGroupId, groupName: routeGroupName, groupAvatar: routeGroupAvatar } = routeParams; // 当前会话ID(可能在新建私聊会话后动态更新) const [conversationId, setConversationId] = useState(routeConversationId); @@ -147,13 +151,18 @@ export const useChatScreen = (props?: ChatScreenProps) => { conversation, } = useChat(conversationId); + // 统一入口:优先路由参数,其次从 conversation 对象提取(JPUSH 进入时 conversation 异步加载) + const effectiveGroupId = routeGroupId || (isGroupChat && conversation?.group?.id) || null; + const effectiveGroupName = routeGroupName || (isGroupChat && conversation?.group?.name) || null; + const effectiveGroupAvatar = routeGroupAvatar || (isGroupChat && conversation?.group?.avatar) || null; + // 本地状态 const [currentUserId, setCurrentUserId] = useState(''); // 【新架构】使用 MessageManager 获取群聊输入状态和禁言状态 - const { typingUsers: groupTypingUsers } = useGroupTyping(routeGroupId ? String(routeGroupId) : null); + const { typingUsers: groupTypingUsers } = useGroupTyping(effectiveGroupId ? String(effectiveGroupId) : null); const { isMuted: isMutedFromManager, setMuted: setMutedStatus } = useGroupMuted( - routeGroupId ? String(routeGroupId) : null, + effectiveGroupId ? String(effectiveGroupId) : null, currentUserId ); @@ -202,8 +211,18 @@ export const useChatScreen = (props?: ChatScreenProps) => { const [selectedMessageId, setSelectedMessageId] = useState(null); // 群聊相关状态 - const [groupInfo, setGroupInfo] = useState<{ name?: string; member_count?: number } | null>(null); + const [groupInfo, setGroupInfo] = useState<{ name?: string; member_count?: number; avatar?: string | null } | null>(null); const [groupMembers, setGroupMembers] = useState([]); + + // 群名/头像:优先使用路由参数(JPUSH extras),其次从 conversation 对象获取 + useEffect(() => { + if (!isGroupChat) return; + const name = effectiveGroupName; + const avatar = effectiveGroupAvatar; + if (name || avatar) { + setGroupInfo(prev => prev ? prev : { name: name || undefined, avatar: avatar || null }); + } + }, [isGroupChat, effectiveGroupName, effectiveGroupAvatar]); const [currentUserRole, setCurrentUserRole] = useState('member'); const [mentionQuery, setMentionQuery] = useState(''); const [selectedMentions, setSelectedMentions] = useState([]); @@ -453,14 +472,14 @@ export const useChatScreen = (props?: ChatScreenProps) => { // 群聊模式:获取群组信息和成员列表 useEffect(() => { - if (!isGroupChat || !routeGroupId) return; + if (!isGroupChat || !effectiveGroupId) return; const fetchGroupInfo = async () => { try { - const group = await groupManager.getGroup(routeGroupId); + const group = await groupManager.getGroup(effectiveGroupId); setGroupInfo(group); - const membersResponse = await groupManager.getMembers(routeGroupId, 1, MEMBERS_PAGE_SIZE); + const membersResponse = await groupManager.getMembers(effectiveGroupId, 1, MEMBERS_PAGE_SIZE); const members = membersResponse.list || []; setGroupMembers(members); @@ -470,7 +489,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { } try { - const myInfo = await groupService.getMyMemberInfo(routeGroupId); + const myInfo = await groupService.getMyMemberInfo(effectiveGroupId); setIsMuted(myInfo.is_muted); setMutedStatus(myInfo.is_muted); setMuteAll(myInfo.mute_all); @@ -508,7 +527,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { if (currentUserId) { fetchGroupInfo(); } - }, [isGroupChat, routeGroupId, currentUserId, setMutedStatus]); + }, [isGroupChat, effectiveGroupId, currentUserId, setMutedStatus]); // 私聊模式:创建或获取会话 useEffect(() => { @@ -1066,7 +1085,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { return; } - if (isGroupChat && routeGroupId) { + if (isGroupChat && effectiveGroupId) { await messageService.sendMessageByAction('group', conversationId, segments); setInputText(''); @@ -1097,7 +1116,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { pendingAttachments, conversationId, isGroupChat, - routeGroupId, + effectiveGroupId, sendMessageViaManager, isMuted, muteAll, @@ -1264,7 +1283,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { try { const segments = buildImageSegments(stickerUrl, stickerUrl, undefined, undefined, replyingTo); - if (isGroupChat && routeGroupId) { + if (isGroupChat && effectiveGroupId) { await messageService.sendMessageByAction('group', conversationId, segments); setReplyingTo(null); } else { @@ -1282,7 +1301,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { } finally { setSendingImage(false); } - }, [conversationId, isGroupChat, routeGroupId, sendMessageViaManager, isMuted, muteAll, buildImageSegments, replyingTo, canSendPrivateImage, scrollToLatest]); + }, [conversationId, isGroupChat, effectiveGroupId, sendMessageViaManager, isMuted, muteAll, buildImageSegments, replyingTo, canSendPrivateImage, scrollToLatest]); // 切换表情面板 const toggleEmojiPanel = useCallback(() => { @@ -1310,7 +1329,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { // 撤回消息 - 现在通过 MessageManager 处理 const handleRecall = useCallback(async (messageId: string) => { try { - if (isGroupChat && routeGroupId) { + if (isGroupChat && effectiveGroupId) { await messageService.recallMessage(messageId); } else { await messageService.recallMessage(messageId); @@ -1320,7 +1339,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { console.error('撤回消息失败:', error); Alert.alert('撤回失败', '无法撤回消息'); } - }, [isGroupChat, routeGroupId, conversationId]); + }, [isGroupChat, effectiveGroupId, conversationId]); // 长按消息显示操作菜单 const handleLongPressMessage = useCallback((message: GroupMessage, position?: MenuPosition) => { @@ -1455,17 +1474,17 @@ export const useChatScreen = (props?: ChatScreenProps) => { // 导航到群组信息或用户资料 const navigateToInfo = useCallback(() => { - if (isGroupChat && routeGroupId) { - router.push(hrefs.hrefGroupInfo(routeGroupId, conversationId || undefined)); + if (isGroupChat && effectiveGroupId) { + router.push(hrefs.hrefGroupInfo(effectiveGroupId, conversationId || undefined)); } else if (otherUser?.id) { router.push(hrefs.hrefUserProfile(String(otherUser.id))); } - }, [isGroupChat, routeGroupId, otherUser, conversationId]); + }, [isGroupChat, effectiveGroupId, otherUser, conversationId]); // 导航到聊天管理页面 const navigateToChatSettings = useCallback(() => { - if (isGroupChat && routeGroupId) { - router.push(hrefs.hrefGroupInfo(routeGroupId, conversationId || undefined)); + if (isGroupChat && effectiveGroupId) { + router.push(hrefs.hrefGroupInfo(effectiveGroupId, conversationId || undefined)); } else if (otherUser?.id && conversationId) { router.push( hrefs.hrefPrivateChatInfo({ @@ -1476,7 +1495,7 @@ export const useChatScreen = (props?: ChatScreenProps) => { }) ); } - }, [isGroupChat, routeGroupId, otherUser, conversationId]); + }, [isGroupChat, effectiveGroupId, otherUser, conversationId]); return { // 状态 @@ -1512,8 +1531,8 @@ export const useChatScreen = (props?: ChatScreenProps) => { muteAll, followRestrictionHint, canSendPrivateImage, - routeGroupId, - routeGroupName, + effectiveGroupId, + effectiveGroupName, otherUserLastReadSeq, messageMap, loadingMore, diff --git a/src/stores/message/MessageManager.ts b/src/stores/message/MessageManager.ts index 86806ca..c95666b 100644 --- a/src/stores/message/MessageManager.ts +++ b/src/stores/message/MessageManager.ts @@ -344,6 +344,11 @@ class MessageManager { } const task = (async () => { + // 确保会话数据在 store 中(JPUSH 通知进入时 store 可能还没有此会话) + const store = useMessageStore.getState(); + if (!store.getConversation(normalizedId)) { + await this.fetchConversationDetail(normalizedId); + } await this.fetchMessages(normalizedId); })(); this.activatingConversationTasks.set(normalizedId, task);