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
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}` : ''}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user