feat(message): support group avatar in navigation and chat screen
All checks were successful
Frontend CI / ota-android (push) Successful in 1m50s
Frontend CI / ota-ios (push) Successful in 1m49s
Frontend CI / build-and-push-web (push) Successful in 2m55s
Frontend CI / build-android-apk (push) Successful in 1h10m58s

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
This commit is contained in:
2026-05-13 00:31:44 +08:00
parent 7e0436a799
commit cbe708b53b
6 changed files with 57 additions and 28 deletions

View File

@@ -161,6 +161,7 @@ function NotificationBootstrap() {
const conversationType = extras.conversation_type || extras.conversationType; const conversationType = extras.conversation_type || extras.conversationType;
const groupId = extras.group_id || extras.groupId; const groupId = extras.group_id || extras.groupId;
const groupName = extras.group_name || extras.groupName; const groupName = extras.group_name || extras.groupName;
const groupAvatar = extras.group_avatar || extras.groupAvatar;
if (conversationId) { if (conversationId) {
const isGroup = conversationType === 'group'; const isGroup = conversationType === 'group';
@@ -169,6 +170,7 @@ function NotificationBootstrap() {
q.set('isGroupChat', '1'); q.set('isGroupChat', '1');
if (groupId) q.set('groupId', groupId); if (groupId) q.set('groupId', groupId);
if (groupName) q.set('groupName', groupName); if (groupName) q.set('groupName', groupName);
if (groupAvatar) q.set('groupAvatar', groupAvatar);
} else if (senderId) { } else if (senderId) {
q.set('userId', senderId); q.set('userId', senderId);
} }

View File

@@ -86,13 +86,15 @@ export function hrefChat(params: {
isGroupChat?: boolean; isGroupChat?: boolean;
groupId?: string; groupId?: string;
groupName?: string; groupName?: string;
groupAvatar?: string;
}): string { }): string {
const { conversationId, userId, isGroupChat, groupId, groupName } = params; const { conversationId, userId, isGroupChat, groupId, groupName, groupAvatar } = params;
const q = new URLSearchParams(); const q = new URLSearchParams();
if (userId) q.set('userId', userId); if (userId) q.set('userId', userId);
if (isGroupChat) q.set('isGroupChat', '1'); if (isGroupChat) q.set('isGroupChat', '1');
if (groupId != null && groupId !== '') q.set('groupId', String(groupId)); if (groupId != null && groupId !== '') q.set('groupId', String(groupId));
if (groupName) q.set('groupName', groupName); if (groupName) q.set('groupName', groupName);
if (groupAvatar) q.set('groupAvatar', groupAvatar);
const qs = q.toString(); const qs = q.toString();
return `/chat/${encodeURIComponent(conversationId)}${qs ? `?${qs}` : ''}`; return `/chat/${encodeURIComponent(conversationId)}${qs ? `?${qs}` : ''}`;
} }

View File

@@ -165,8 +165,8 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
muteAll, muteAll,
followRestrictionHint, followRestrictionHint,
canSendPrivateImage, canSendPrivateImage,
routeGroupId, effectiveGroupId,
routeGroupName, effectiveGroupName,
otherUserLastReadSeq, otherUserLastReadSeq,
messageMap, messageMap,
loadingMore, loadingMore,
@@ -458,7 +458,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
isGroupChat={isGroupChat} isGroupChat={isGroupChat}
groupInfo={groupInfo} groupInfo={groupInfo}
otherUser={otherUser} otherUser={otherUser}
routeGroupName={routeGroupName ?? undefined} routeGroupName={effectiveGroupName ?? undefined}
typingHint={typingHint} typingHint={typingHint}
onBack={props.onEmbeddedBack ? props.onEmbeddedBack : () => router.back()} onBack={props.onEmbeddedBack ? props.onEmbeddedBack : () => router.back()}
onTitlePress={navigateToInfo} onTitlePress={navigateToInfo}
@@ -669,7 +669,7 @@ export const ChatScreen: React.FC<ChatScreenProps> = (props) => {
{/* 群信息侧边栏面板 - 仅大屏幕显示 */} {/* 群信息侧边栏面板 - 仅大屏幕显示 */}
<GroupInfoPanel <GroupInfoPanel
visible={showGroupInfoPanel} visible={showGroupInfoPanel}
groupId={routeGroupId ? String(routeGroupId) : undefined} groupId={effectiveGroupId ? String(effectiveGroupId) : undefined}
groupInfo={groupInfo as any} groupInfo={groupInfo as any}
conversationId={conversationId || undefined} conversationId={conversationId || undefined}
currentUserId={currentUserId} currentUserId={currentUserId}

View File

@@ -285,6 +285,7 @@ export const MessageListScreen: React.FC = () => {
conversationId: String(conversation.id), conversationId: String(conversation.id),
groupId: String(conversation.group.id), groupId: String(conversation.group.id),
groupName: conversation.group.name, groupName: conversation.group.name,
groupAvatar: conversation.group.avatar,
isGroupChat: true, isGroupChat: true,
}) })
); );

View File

@@ -96,6 +96,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
isGroupChat?: string | string[]; isGroupChat?: string | string[];
groupId?: string | string[]; groupId?: string | string[];
groupName?: string | string[]; groupName?: string | string[];
groupAvatar?: string | string[];
}>(); }>();
// 路由参数(动态段 + query群 ID 勿用 Number(),避免超过 2^53-1 时精度丢失 // 路由参数(动态段 + query群 ID 勿用 Number(),避免超过 2^53-1 时精度丢失
// 嵌入式模式下优先使用 props否则从路由参数获取 // 嵌入式模式下优先使用 props否则从路由参数获取
@@ -107,6 +108,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
isGroupChat: props.embeddedIsGroupChat ?? false, isGroupChat: props.embeddedIsGroupChat ?? false,
groupId: props.embeddedGroupId ?? null, groupId: props.embeddedGroupId ?? null,
groupName: props.embeddedGroupName ?? null, groupName: props.embeddedGroupName ?? null,
groupAvatar: null as string | null,
}; };
} }
const isGroupFlag = firstRouteParam(rawParams.isGroupChat); const isGroupFlag = firstRouteParam(rawParams.isGroupChat);
@@ -116,6 +118,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
isGroupChat: isGroupFlag === '1' || isGroupFlag === 'true', isGroupChat: isGroupFlag === '1' || isGroupFlag === 'true',
groupId: firstRouteParam(rawParams.groupId), groupId: firstRouteParam(rawParams.groupId),
groupName: firstRouteParam(rawParams.groupName), groupName: firstRouteParam(rawParams.groupName),
groupAvatar: firstRouteParam(rawParams.groupAvatar),
}; };
}, [ }, [
props?.embeddedConversationId, props?.embeddedConversationId,
@@ -127,9 +130,10 @@ export const useChatScreen = (props?: ChatScreenProps) => {
rawParams.isGroupChat, rawParams.isGroupChat,
rawParams.groupId, rawParams.groupId,
rawParams.groupName, 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可能在新建私聊会话后动态更新 // 当前会话ID可能在新建私聊会话后动态更新
const [conversationId, setConversationId] = useState<string | null>(routeConversationId); const [conversationId, setConversationId] = useState<string | null>(routeConversationId);
@@ -147,13 +151,18 @@ export const useChatScreen = (props?: ChatScreenProps) => {
conversation, conversation,
} = useChat(conversationId); } = 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<string>(''); const [currentUserId, setCurrentUserId] = useState<string>('');
// 【新架构】使用 MessageManager 获取群聊输入状态和禁言状态 // 【新架构】使用 MessageManager 获取群聊输入状态和禁言状态
const { typingUsers: groupTypingUsers } = useGroupTyping(routeGroupId ? String(routeGroupId) : null); const { typingUsers: groupTypingUsers } = useGroupTyping(effectiveGroupId ? String(effectiveGroupId) : null);
const { isMuted: isMutedFromManager, setMuted: setMutedStatus } = useGroupMuted( const { isMuted: isMutedFromManager, setMuted: setMutedStatus } = useGroupMuted(
routeGroupId ? String(routeGroupId) : null, effectiveGroupId ? String(effectiveGroupId) : null,
currentUserId currentUserId
); );
@@ -202,8 +211,18 @@ export const useChatScreen = (props?: ChatScreenProps) => {
const [selectedMessageId, setSelectedMessageId] = useState<string | null>(null); const [selectedMessageId, setSelectedMessageId] = useState<string | null>(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<GroupMemberResponse[]>([]); const [groupMembers, setGroupMembers] = useState<GroupMemberResponse[]>([]);
// 群名/头像优先使用路由参数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<UserRole>('member'); const [currentUserRole, setCurrentUserRole] = useState<UserRole>('member');
const [mentionQuery, setMentionQuery] = useState<string>(''); const [mentionQuery, setMentionQuery] = useState<string>('');
const [selectedMentions, setSelectedMentions] = useState<string[]>([]); const [selectedMentions, setSelectedMentions] = useState<string[]>([]);
@@ -453,14 +472,14 @@ export const useChatScreen = (props?: ChatScreenProps) => {
// 群聊模式:获取群组信息和成员列表 // 群聊模式:获取群组信息和成员列表
useEffect(() => { useEffect(() => {
if (!isGroupChat || !routeGroupId) return; if (!isGroupChat || !effectiveGroupId) return;
const fetchGroupInfo = async () => { const fetchGroupInfo = async () => {
try { try {
const group = await groupManager.getGroup(routeGroupId); const group = await groupManager.getGroup(effectiveGroupId);
setGroupInfo(group); 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 || []; const members = membersResponse.list || [];
setGroupMembers(members); setGroupMembers(members);
@@ -470,7 +489,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
} }
try { try {
const myInfo = await groupService.getMyMemberInfo(routeGroupId); const myInfo = await groupService.getMyMemberInfo(effectiveGroupId);
setIsMuted(myInfo.is_muted); setIsMuted(myInfo.is_muted);
setMutedStatus(myInfo.is_muted); setMutedStatus(myInfo.is_muted);
setMuteAll(myInfo.mute_all); setMuteAll(myInfo.mute_all);
@@ -508,7 +527,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
if (currentUserId) { if (currentUserId) {
fetchGroupInfo(); fetchGroupInfo();
} }
}, [isGroupChat, routeGroupId, currentUserId, setMutedStatus]); }, [isGroupChat, effectiveGroupId, currentUserId, setMutedStatus]);
// 私聊模式:创建或获取会话 // 私聊模式:创建或获取会话
useEffect(() => { useEffect(() => {
@@ -1066,7 +1085,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
return; return;
} }
if (isGroupChat && routeGroupId) { if (isGroupChat && effectiveGroupId) {
await messageService.sendMessageByAction('group', conversationId, segments); await messageService.sendMessageByAction('group', conversationId, segments);
setInputText(''); setInputText('');
@@ -1097,7 +1116,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
pendingAttachments, pendingAttachments,
conversationId, conversationId,
isGroupChat, isGroupChat,
routeGroupId, effectiveGroupId,
sendMessageViaManager, sendMessageViaManager,
isMuted, isMuted,
muteAll, muteAll,
@@ -1264,7 +1283,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
try { try {
const segments = buildImageSegments(stickerUrl, stickerUrl, undefined, undefined, replyingTo); const segments = buildImageSegments(stickerUrl, stickerUrl, undefined, undefined, replyingTo);
if (isGroupChat && routeGroupId) { if (isGroupChat && effectiveGroupId) {
await messageService.sendMessageByAction('group', conversationId, segments); await messageService.sendMessageByAction('group', conversationId, segments);
setReplyingTo(null); setReplyingTo(null);
} else { } else {
@@ -1282,7 +1301,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
} finally { } finally {
setSendingImage(false); 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(() => { const toggleEmojiPanel = useCallback(() => {
@@ -1310,7 +1329,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
// 撤回消息 - 现在通过 MessageManager 处理 // 撤回消息 - 现在通过 MessageManager 处理
const handleRecall = useCallback(async (messageId: string) => { const handleRecall = useCallback(async (messageId: string) => {
try { try {
if (isGroupChat && routeGroupId) { if (isGroupChat && effectiveGroupId) {
await messageService.recallMessage(messageId); await messageService.recallMessage(messageId);
} else { } else {
await messageService.recallMessage(messageId); await messageService.recallMessage(messageId);
@@ -1320,7 +1339,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
console.error('撤回消息失败:', error); console.error('撤回消息失败:', error);
Alert.alert('撤回失败', '无法撤回消息'); Alert.alert('撤回失败', '无法撤回消息');
} }
}, [isGroupChat, routeGroupId, conversationId]); }, [isGroupChat, effectiveGroupId, conversationId]);
// 长按消息显示操作菜单 // 长按消息显示操作菜单
const handleLongPressMessage = useCallback((message: GroupMessage, position?: MenuPosition) => { const handleLongPressMessage = useCallback((message: GroupMessage, position?: MenuPosition) => {
@@ -1455,17 +1474,17 @@ export const useChatScreen = (props?: ChatScreenProps) => {
// 导航到群组信息或用户资料 // 导航到群组信息或用户资料
const navigateToInfo = useCallback(() => { const navigateToInfo = useCallback(() => {
if (isGroupChat && routeGroupId) { if (isGroupChat && effectiveGroupId) {
router.push(hrefs.hrefGroupInfo(routeGroupId, conversationId || undefined)); router.push(hrefs.hrefGroupInfo(effectiveGroupId, conversationId || undefined));
} else if (otherUser?.id) { } else if (otherUser?.id) {
router.push(hrefs.hrefUserProfile(String(otherUser.id))); router.push(hrefs.hrefUserProfile(String(otherUser.id)));
} }
}, [isGroupChat, routeGroupId, otherUser, conversationId]); }, [isGroupChat, effectiveGroupId, otherUser, conversationId]);
// 导航到聊天管理页面 // 导航到聊天管理页面
const navigateToChatSettings = useCallback(() => { const navigateToChatSettings = useCallback(() => {
if (isGroupChat && routeGroupId) { if (isGroupChat && effectiveGroupId) {
router.push(hrefs.hrefGroupInfo(routeGroupId, conversationId || undefined)); router.push(hrefs.hrefGroupInfo(effectiveGroupId, conversationId || undefined));
} else if (otherUser?.id && conversationId) { } else if (otherUser?.id && conversationId) {
router.push( router.push(
hrefs.hrefPrivateChatInfo({ hrefs.hrefPrivateChatInfo({
@@ -1476,7 +1495,7 @@ export const useChatScreen = (props?: ChatScreenProps) => {
}) })
); );
} }
}, [isGroupChat, routeGroupId, otherUser, conversationId]); }, [isGroupChat, effectiveGroupId, otherUser, conversationId]);
return { return {
// 状态 // 状态
@@ -1512,8 +1531,8 @@ export const useChatScreen = (props?: ChatScreenProps) => {
muteAll, muteAll,
followRestrictionHint, followRestrictionHint,
canSendPrivateImage, canSendPrivateImage,
routeGroupId, effectiveGroupId,
routeGroupName, effectiveGroupName,
otherUserLastReadSeq, otherUserLastReadSeq,
messageMap, messageMap,
loadingMore, loadingMore,

View File

@@ -344,6 +344,11 @@ class MessageManager {
} }
const task = (async () => { const task = (async () => {
// 确保会话数据在 store 中JPUSH 通知进入时 store 可能还没有此会话)
const store = useMessageStore.getState();
if (!store.getConversation(normalizedId)) {
await this.fetchConversationDetail(normalizedId);
}
await this.fetchMessages(normalizedId); await this.fetchMessages(normalizedId);
})(); })();
this.activatingConversationTasks.set(normalizedId, task); this.activatingConversationTasks.set(normalizedId, task);