/** * GroupInfoScreen 群组信息/设置界面 * 显示群组详细信息,提供群组管理功能 * 支持响应式布局 */ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { View, StyleSheet, ScrollView, TouchableOpacity, Alert, Clipboard, TextInput, Modal, Switch, KeyboardAvoidingView, Platform, Image, Dimensions, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useFocusEffect } from '@react-navigation/native'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import * as ImagePicker from 'expo-image-picker'; import { spacing, fontSizes, borderRadius, shadows, useAppColors, type AppColors, } from '../../theme'; import { useAuthStore } from '../../stores'; import { groupService } from '@/services/message'; import { uploadService } from '@/services/upload'; import { messageService } from '@/services/message'; import { Avatar, Text, Button, Loading, Divider } from '../../components/common'; import { useResponsive, useBreakpointGTE } from '../../hooks/useResponsive'; import { GroupResponse, GroupMemberResponse, GroupAnnouncementResponse, JoinType, } from '../../types/dto'; import { User } from '../../types'; import { blurActiveElement } from '../../infrastructure/platform'; import { firstRouteParam } from '../../navigation/paramUtils'; import * as hrefs from '../../navigation/hrefs'; import { messageManager } from '../../stores/message'; import { useMessageStore } from '../../stores/message/store'; import { groupManager } from '../../stores/group'; import MutualFollowSelectorModal from './components/MutualFollowSelectorModal'; import { AppBackButton } from '../../components/common'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); // 加群方式选项 const JOIN_TYPE_OPTIONS: { value: JoinType; label: string; icon: string; desc: string }[] = [ { value: 0, label: '允许任何人加入', icon: 'earth', desc: '任何人都可以直接加入群聊' }, { value: 1, label: '需要管理员审批', icon: 'shield-check', desc: '新成员需要管理员审核' }, { value: 2, label: '不允许任何人加入', icon: 'lock', desc: '只能通过邀请加入群聊' }, ]; const GroupInfoScreen: React.FC = () => { const colors = useAppColors(); const styles = useMemo(() => createGroupInfoStyles(colors), [colors]); const router = useRouter(); const { groupId: groupIdParam, conversationId: conversationIdParam } = useLocalSearchParams<{ groupId?: string | string[]; conversationId?: string | string[]; }>(); const groupId = firstRouteParam(groupIdParam) ?? ''; const conversationId = firstRouteParam(conversationIdParam); const { currentUser } = useAuthStore(); // 响应式布局 const { isDesktop, isTablet, width } = useResponsive(); const isWideScreen = useBreakpointGTE('lg'); // 计算成员网格列数 const memberColumns = useMemo(() => { if (width >= 1200) return 6; if (width >= 900) return 5; if (width >= 768) return 4; return 5; // 移动端默认 }, [width]); // 群组信息状态 const [group, setGroup] = useState(null); const [members, setMembers] = useState([]); const [announcements, setAnnouncements] = useState([]); const [currentMember, setCurrentMember] = useState(null); const [loading, setLoading] = useState(true); const [isPinned, setIsPinned] = useState(false); const [pinLoading, setPinLoading] = useState(false); const isNotificationMuted = useMessageStore(state => conversationId ? (state.notificationMutedMap.get(conversationId) || false) : false ); // 编辑模态框状态 const [editModalVisible, setEditModalVisible] = useState(false); const [editName, setEditName] = useState(''); const [editDescription, setEditDescription] = useState(''); const [editAvatar, setEditAvatar] = useState(''); const [saving, setSaving] = useState(false); const [uploadingAvatar, setUploadingAvatar] = useState(false); // 群公告模态框状态 const [announcementModalVisible, setAnnouncementModalVisible] = useState(false); const [newAnnouncement, setNewAnnouncement] = useState(''); // 转让群主模态框状态 const [transferModalVisible, setTransferModalVisible] = useState(false); const [transferUserId, setTransferUserId] = useState(''); // 加群方式模态框状态 const [joinTypeModalVisible, setJoinTypeModalVisible] = useState(false); // 邀请成员模态框状态 const [inviteModalVisible, setInviteModalVisible] = useState(false); const [inviting, setInviting] = useState(false); useEffect(() => { if (editModalVisible || announcementModalVisible || transferModalVisible || joinTypeModalVisible || inviteModalVisible) { blurActiveElement(); } }, [editModalVisible, announcementModalVisible, transferModalVisible, joinTypeModalVisible, inviteModalVisible]); // 计算当前用户的角色 const isOwner = currentMember?.role === 'owner'; const isAdmin = currentMember?.role === 'admin' || isOwner; // 加载群组信息 const loadGroupInfo = useCallback(async () => { if (!groupId) return; try { setLoading(true); // 并行加载群组信息和成员列表 const [groupData, membersData] = await Promise.all([ groupManager.getGroup(groupId, true), groupManager.getMembers(groupId, 1, 100, true), ]); setGroup(groupData); setMembers(membersData.list); // 查找当前用户的成员信息 const myMember = membersData.list.find(m => m.user_id === currentUser?.id); setCurrentMember(myMember || null); // 加载群公告 try { const announcementsData = await groupService.getAnnouncements(groupId, 1, 10); setAnnouncements(announcementsData.list); } catch (e) { console.error('加载群公告失败:', e); } } catch (error) { console.error('加载群组信息失败:', error); Alert.alert('错误', '加载群组信息失败', [ { text: '确定', onPress: () => router.back() }, ]); } finally { setLoading(false); } }, [groupId, currentUser]); // 加载会话置顶状态 const loadPinStatus = useCallback(async () => { if (!conversationId) { setIsPinned(false); return; } const conv = messageManager.getConversation(conversationId); if (conv) { setIsPinned(Boolean(conv.is_pinned)); return; } try { const detail = await messageService.getConversationById(conversationId); setIsPinned(Boolean(detail.is_pinned)); } catch (error) { console.error('加载置顶状态失败:', error); } }, [conversationId]); // 页面聚焦时刷新数据 useFocusEffect( useCallback(() => { loadGroupInfo(); loadPinStatus(); }, [loadGroupInfo, loadPinStatus]) ); // 设置会话置顶 const handleTogglePinned = async () => { if (!conversationId || pinLoading) return; const nextValue = !isPinned; setIsPinned(nextValue); setPinLoading(true); try { await messageService.setConversationPinned(conversationId, nextValue); messageManager.updateConversation(conversationId, { is_pinned: nextValue }); } catch (error: any) { setIsPinned(!nextValue); Alert.alert('错误', error?.message || '设置置顶失败'); } finally { setPinLoading(false); } }; // 设置会话免打扰 const handleToggleNotificationMuted = async () => { if (!conversationId) return; try { await messageManager.toggleNotificationMuted(conversationId, !isNotificationMuted); } catch (error: any) { Alert.alert('错误', error?.message || '设置免打扰失败'); } }; // 打开编辑模态框 const openEditModal = () => { if (group) { setEditName(group.name); setEditDescription(group.description || ''); setEditAvatar(group.avatar || ''); setEditModalVisible(true); } }; // 选择群头像 const handleSelectGroupAvatar = async () => { try { const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (status !== 'granted') { Alert.alert('提示', '需要访问相册权限才能选择头像'); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: 'images', allowsEditing: true, aspect: [1, 1], quality: 0.8, }); if (!result.canceled && result.assets && result.assets.length > 0) { const selectedAsset = result.assets[0]; setUploadingAvatar(true); try { const uploadResult = await uploadService.uploadGroupAvatar({ uri: selectedAsset.uri, type: selectedAsset.mimeType || 'image/jpeg', }); if (uploadResult && uploadResult.url) { setEditAvatar(uploadResult.url); } else { Alert.alert('上传失败', '头像上传失败,请重试'); } } catch (error) { console.error('上传头像失败:', error); Alert.alert('上传失败', '头像上传失败,请重试'); } finally { setUploadingAvatar(false); } } } catch (error) { console.error('选择头像失败:', error); Alert.alert('错误', '选择头像时发生错误'); } }; // 保存群组信息 const handleSaveGroupInfo = async () => { if (!editName.trim()) { Alert.alert('提示', '群名称不能为空'); return; } setSaving(true); try { // 更新群名称 await groupService.setGroupName(groupId, editName.trim()); // 如果头像有变化,更新群头像 if (editAvatar !== (group?.avatar || '')) { await groupService.setGroupAvatar(groupId, editAvatar); } // 重新获取群信息 const updatedGroup = await groupManager.getGroup(groupId, true); setGroup(updatedGroup); setEditModalVisible(false); Alert.alert('成功', '群信息已更新'); } catch (error: any) { console.error('更新群信息失败:', error); Alert.alert('错误', error.message || '更新群信息失败'); } finally { setSaving(false); } }; // 设置全员禁言 const handleToggleMuteAll = async () => { if (!group) return; const newMuteAll = !group.mute_all; Alert.alert( newMuteAll ? '开启全员禁言' : '关闭全员禁言', newMuteAll ? '确定要开启全员禁言吗?开启后只有管理员可以发言。' : '确定要关闭全员禁言吗?', [ { text: '取消', style: 'cancel' }, { text: '确定', onPress: async () => { try { await groupService.setMuteAll(groupId, { mute_all: newMuteAll }); setGroup({ ...group, mute_all: newMuteAll }); Alert.alert('成功', newMuteAll ? '已开启全员禁言' : '已关闭全员禁言'); } catch (error: any) { console.error('设置全员禁言失败:', error); Alert.alert('错误', error.message || '设置失败'); } }, }, ] ); }; // 设置加群方式 const handleSetJoinType = async (joinType: JoinType) => { try { await groupService.setJoinType(groupId, { join_type: joinType }); if (group) { setGroup({ ...group, join_type: joinType }); } setJoinTypeModalVisible(false); Alert.alert('成功', '加群方式已更新'); } catch (error: any) { console.error('设置加群方式失败:', error); Alert.alert('错误', error.message || '设置失败'); } }; // 发布群公告 const handlePublishAnnouncement = async () => { if (!newAnnouncement.trim()) { Alert.alert('提示', '公告内容不能为空'); return; } setSaving(true); try { await groupService.createAnnouncement(groupId, { content: newAnnouncement.trim(), }); setNewAnnouncement(''); setAnnouncementModalVisible(false); // 刷新公告列表 const announcementsData = await groupService.getAnnouncements(groupId, 1, 10); setAnnouncements(announcementsData.list); Alert.alert('成功', '群公告已发布'); } catch (error: any) { console.error('发布群公告失败:', error); Alert.alert('错误', error.message || '发布失败'); } finally { setSaving(false); } }; // 转让群主 const handleTransferOwner = async () => { if (!transferUserId) { Alert.alert('提示', '请选择新群主'); return; } Alert.alert( '转让群主', '确定要将群主转让给该成员吗?转让后您将成为普通成员。', [ { text: '取消', style: 'cancel' }, { text: '确定', onPress: async () => { try { await groupService.transferOwner(groupId, { new_owner_id: transferUserId, }); setTransferModalVisible(false); Alert.alert('成功', '群主已转让', [ { text: '确定', onPress: () => loadGroupInfo() }, ]); } catch (error: any) { console.error('转让群主失败:', error); Alert.alert('错误', error.message || '转让失败'); } }, }, ] ); }; // 解散群组 const handleDissolveGroup = () => { Alert.alert( '解散群组', '确定要解散群组吗?此操作不可撤销,所有成员将被移出。', [ { text: '取消', style: 'cancel' }, { text: '解散', style: 'destructive', onPress: async () => { try { await groupService.dissolveGroup(groupId); Alert.alert('成功', '群组已解散', [ { text: '确定', onPress: () => router.back() }, ]); } catch (error: any) { console.error('解散群组失败:', error); Alert.alert('错误', error.message || '解散失败'); } }, }, ] ); }; // 退出群聊 const handleLeaveGroup = () => { Alert.alert( '退出群聊', '确定要退出群聊吗?', [ { text: '取消', style: 'cancel' }, { text: '退出', style: 'destructive', onPress: async () => { try { await groupService.leaveGroup(groupId); Alert.alert('成功', '已退出群聊', [ { text: '确定', onPress: () => router.back() }, ]); } catch (error: any) { console.error('退出群聊失败:', error); Alert.alert('错误', error.message || '退出失败'); } }, }, ] ); }; // 打开邀请成员模态框 const openInviteModal = () => { setInviteModalVisible(true); }; // 邀请成员 const handleInviteMembers = async (selectedUsers: User[]) => { if (selectedUsers.length === 0) { Alert.alert('提示', '请选择要邀请的成员'); return; } setInviting(true); try { const memberIds = selectedUsers.map(user => user.id); await groupService.inviteMembers(groupId, { member_ids: memberIds }); setInviteModalVisible(false); Alert.alert('成功', '邀请已发送'); // 刷新成员列表 loadGroupInfo(); } catch (error: any) { console.error('邀请成员失败:', error); Alert.alert('错误', error.message || '邀请失败'); } finally { setInviting(false); } }; // 跳转到成员管理 const goToMembers = () => { router.push(hrefs.hrefGroupMembers(groupId)); }; // 获取加群方式文本 const getJoinTypeText = (joinType: JoinType): string => { const option = JOIN_TYPE_OPTIONS.find(o => o.value === joinType); return option?.label || '未知'; }; // 获取加群方式图标 const getJoinTypeIcon = (joinType: JoinType): string => { const option = JOIN_TYPE_OPTIONS.find(o => o.value === joinType); return option?.icon || 'help-circle'; }; const handleCopyGroupNo = () => { if (!group) return; Clipboard.setString(String(group.id)); Alert.alert('已复制', '群号已复制到剪贴板'); }; const formatGroupNo = (id: string) => { const raw = id; if (raw.length <= 12) return raw; return `${raw.slice(0, 6)}...${raw.slice(-4)}`; }; // 渲染设置项 const renderSettingItem = ( icon: string, title: string, value?: string, onPress?: () => void, danger: boolean = false ) => ( {title} {value && ( {value} )} {onPress && ( )} ); if (loading) { return ( router.back()} /> 群聊信息 ); } if (!group) { return ( router.back()} /> 群聊信息 群组信息不存在 ); } return ( router.back()} /> 群聊信息 {/* 群组基本信息 */} {group.name} {group.member_count} 人 · {getJoinTypeText(group.join_type)} 群号:{formatGroupNo(group.id)} 复制 {group.description ? ( {group.description} ) : ( 暂无群描述 )} {/* 群公告 */} {announcements.length > 0 && ( 群公告 {announcements[0].content} {(() => { const d = new Date(announcements[0].created_at); return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString(); })()} )} {/* 成员预览 */} 群成员 {group.member_count}人 {members.slice(0, 8).map((member, index) => ( {member.role === 'owner' && ( )} ))} {group.member_count > 8 && ( +{group.member_count - 8} )} {/* 邀请成员按钮 */} 邀请新成员 {/* 管理员设置 */} {isAdmin && ( 群管理 {renderSettingItem('pencil-outline', '修改群信息', undefined, openEditModal)} {isOwner && renderSettingItem('earth', '加群方式', getJoinTypeText(group.join_type), () => setJoinTypeModalVisible(true))} {isOwner && } {isOwner && ( 全员禁言 {group.mute_all ? '已开启' : '已关闭'} )} {renderSettingItem('bullhorn-outline', '发布群公告', undefined, () => setAnnouncementModalVisible(true))} {isOwner && } {isOwner && renderSettingItem('account-switch', '转让群主', undefined, () => setTransferModalVisible(true))} )} {/* 聊天设置 */} 聊天设置 置顶群聊 {conversationId ? (isPinned ? '已置顶' : '未置顶') : '请从聊天页面进入后设置'} 消息免打扰 {conversationId ? (isNotificationMuted ? '已开启' : '未开启') : '请从聊天页面进入后设置'} {/* 危险操作区域 */} {isOwner ? ( 解散群组 ) : ( 退出群聊 )} {/* 编辑群信息模态框 */} setEditModalVisible(false)} > setEditModalVisible(false)}> 取消 编辑群信息 {saving ? '保存中...' : '保存'} {uploadingAvatar ? ( ) : editAvatar ? ( ) : ( )} 群名称 {editName.length}/50 群描述 {editDescription.length}/500 {/* 发布群公告模态框 */} setAnnouncementModalVisible(false)} > setAnnouncementModalVisible(false)}> 取消 发布群公告 {saving ? '发布中...' : '发布'} {newAnnouncement.length}/500 {/* 加群方式模态框 */} setJoinTypeModalVisible(false)} > setJoinTypeModalVisible(false)}> 取消 加群方式 {JOIN_TYPE_OPTIONS.map((option) => ( handleSetJoinType(option.value)} activeOpacity={0.7} > {option.label} {option.desc} {group.join_type === option.value && ( )} ))} {/* 转让群主模态框 */} setTransferModalVisible(false)} > setTransferModalVisible(false)}> 取消 转让群主 选择新的群主(仅显示管理员和普通成员) {members .filter(m => m.role === 'admin' || m.role === 'member') .map((member) => ( setTransferUserId(member.user_id)} activeOpacity={0.7} > {member.user?.nickname || member.nickname} {member.role === 'admin' ? '管理员' : '成员'} {transferUserId === member.user_id && ( )} ))}