2026-03-29 02:34:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* GroupInfoPanel.web.tsx
|
|
|
|
|
|
* Web端群信息侧边栏组件
|
|
|
|
|
|
* 大屏幕模式下从右侧滑入显示
|
|
|
|
|
|
* 实现手机端群信息界面的核心功能
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
|
|
|
|
|
import {
|
|
|
|
|
|
View,
|
|
|
|
|
|
StyleSheet,
|
|
|
|
|
|
TouchableOpacity,
|
|
|
|
|
|
ScrollView,
|
|
|
|
|
|
Dimensions,
|
|
|
|
|
|
Animated,
|
|
|
|
|
|
Alert,
|
|
|
|
|
|
} from 'react-native';
|
|
|
|
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
|
|
|
|
import { Avatar, Text } from '../../../../components/common';
|
|
|
|
|
|
import { useAppColors, spacing, fontSizes, shadows, type AppColors } from '../../../../theme';
|
|
|
|
|
|
import { GroupMemberResponse, GroupResponse, GroupAnnouncementResponse } from '../../../../types/dto';
|
|
|
|
|
|
import { useBreakpointGTE } from '../../../../hooks/useResponsive';
|
|
|
|
|
|
import { groupManager } from '../../../../stores/groupManager';
|
|
|
|
|
|
import { groupService } from '../../../../services/groupService';
|
|
|
|
|
|
|
|
|
|
|
|
const { width: screenWidth } = Dimensions.get('window');
|
|
|
|
|
|
const PANEL_WIDTH = 360;
|
|
|
|
|
|
|
|
|
|
|
|
interface GroupInfoPanelProps {
|
|
|
|
|
|
visible: boolean;
|
|
|
|
|
|
groupId?: string;
|
|
|
|
|
|
groupInfo: GroupResponse | null;
|
|
|
|
|
|
conversationId?: string;
|
|
|
|
|
|
isPinned?: boolean;
|
|
|
|
|
|
currentUserId?: string;
|
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
|
onTogglePin?: (pinned: boolean) => void;
|
|
|
|
|
|
onLeaveGroup?: () => void;
|
|
|
|
|
|
onInviteMembers?: () => void;
|
|
|
|
|
|
onViewAllMembers?: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const GroupInfoPanel: React.FC<GroupInfoPanelProps> = ({
|
|
|
|
|
|
visible,
|
|
|
|
|
|
groupId,
|
|
|
|
|
|
groupInfo,
|
|
|
|
|
|
conversationId,
|
|
|
|
|
|
isPinned,
|
|
|
|
|
|
currentUserId,
|
|
|
|
|
|
onClose,
|
|
|
|
|
|
onTogglePin,
|
|
|
|
|
|
onLeaveGroup,
|
|
|
|
|
|
onInviteMembers,
|
|
|
|
|
|
onViewAllMembers,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const colors = useAppColors();
|
|
|
|
|
|
const styles = useMemo(() => createGroupInfoPanelStyles(colors), [colors]);
|
|
|
|
|
|
const isWideScreen = useBreakpointGTE('lg');
|
|
|
|
|
|
// Web 端不使用 useNativeDriver
|
|
|
|
|
|
const slideAnim = React.useRef(new Animated.Value(PANEL_WIDTH)).current;
|
|
|
|
|
|
const fadeAnim = React.useRef(new Animated.Value(0)).current;
|
|
|
|
|
|
const [members, setMembers] = useState<GroupMemberResponse[]>([]);
|
|
|
|
|
|
const [announcements, setAnnouncements] = useState<GroupAnnouncementResponse[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [showAllMembers, setShowAllMembers] = useState(false);
|
|
|
|
|
|
const [allMembers, setAllMembers] = useState<GroupMemberResponse[]>([]);
|
|
|
|
|
|
const [loadingMoreMembers, setLoadingMoreMembers] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载群成员(初始只加载10个)
|
|
|
|
|
|
const loadMembers = useCallback(async () => {
|
|
|
|
|
|
if (!groupId) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
const response = await groupManager.getMembers(groupId, 1, 10);
|
|
|
|
|
|
setMembers(response.list || []);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[GroupInfoPanel] 加载成员失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [groupId]);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载全部群成员
|
|
|
|
|
|
const loadAllMembers = useCallback(async () => {
|
|
|
|
|
|
if (!groupId) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoadingMoreMembers(true);
|
|
|
|
|
|
const response = await groupManager.getMembers(groupId, 1, 100);
|
|
|
|
|
|
setAllMembers(response.list || []);
|
|
|
|
|
|
setShowAllMembers(true);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[GroupInfoPanel] 加载全部成员失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoadingMoreMembers(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [groupId]);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载群公告
|
|
|
|
|
|
const loadAnnouncements = useCallback(async () => {
|
|
|
|
|
|
if (!groupId) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await groupService.getAnnouncements(groupId, 1, 10);
|
|
|
|
|
|
setAnnouncements(response.list || []);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[GroupInfoPanel] 加载公告失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [groupId]);
|
|
|
|
|
|
|
|
|
|
|
|
// 每次打开面板时重置状态
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (visible) {
|
|
|
|
|
|
setShowAllMembers(false);
|
|
|
|
|
|
setAllMembers([]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [visible]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (visible && groupId) {
|
|
|
|
|
|
loadMembers();
|
|
|
|
|
|
loadAnnouncements();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [visible, groupId, loadMembers, loadAnnouncements]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (visible) {
|
|
|
|
|
|
Animated.parallel([
|
|
|
|
|
|
Animated.timing(slideAnim, {
|
|
|
|
|
|
toValue: 0,
|
|
|
|
|
|
duration: 300,
|
|
|
|
|
|
// Web 端不使用 useNativeDriver
|
|
|
|
|
|
useNativeDriver: false,
|
|
|
|
|
|
}),
|
|
|
|
|
|
Animated.timing(fadeAnim, {
|
|
|
|
|
|
toValue: 1,
|
|
|
|
|
|
duration: 300,
|
|
|
|
|
|
// Web 端不使用 useNativeDriver
|
|
|
|
|
|
useNativeDriver: false,
|
|
|
|
|
|
}),
|
|
|
|
|
|
]).start();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Animated.parallel([
|
|
|
|
|
|
Animated.timing(slideAnim, {
|
|
|
|
|
|
toValue: PANEL_WIDTH,
|
|
|
|
|
|
duration: 300,
|
|
|
|
|
|
// Web 端不使用 useNativeDriver
|
|
|
|
|
|
useNativeDriver: false,
|
|
|
|
|
|
}),
|
|
|
|
|
|
Animated.timing(fadeAnim, {
|
|
|
|
|
|
toValue: 0,
|
|
|
|
|
|
duration: 300,
|
|
|
|
|
|
// Web 端不使用 useNativeDriver
|
|
|
|
|
|
useNativeDriver: false,
|
|
|
|
|
|
}),
|
|
|
|
|
|
]).start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [visible, slideAnim, fadeAnim]);
|
|
|
|
|
|
|
|
|
|
|
|
if (!isWideScreen) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取加群方式文本
|
|
|
|
|
|
const getJoinTypeText = (joinType?: number): string => {
|
|
|
|
|
|
switch (joinType) {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
return '允许任何人加入';
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
return '需要管理员审批';
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
return '不允许任何人加入';
|
|
|
|
|
|
default:
|
|
|
|
|
|
return '未知';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* 遮罩层 */}
|
|
|
|
|
|
{visible && (
|
|
|
|
|
|
<Animated.View style={[styles.overlay, { opacity: fadeAnim }]}>
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={styles.overlayTouchable}
|
|
|
|
|
|
onPress={onClose}
|
|
|
|
|
|
activeOpacity={1}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Animated.View>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 侧边栏面板 */}
|
|
|
|
|
|
<Animated.View
|
|
|
|
|
|
style={[
|
|
|
|
|
|
styles.panel,
|
|
|
|
|
|
{
|
|
|
|
|
|
transform: [{ translateX: slideAnim }],
|
|
|
|
|
|
},
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* 头部 */}
|
|
|
|
|
|
<View style={styles.header}>
|
|
|
|
|
|
<Text style={styles.headerTitle}>群信息</Text>
|
|
|
|
|
|
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
|
|
|
|
|
<MaterialCommunityIcons name="close" size={24} color="#333" />
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
|
|
|
|
|
{/* 群头像和名称 */}
|
|
|
|
|
|
<View style={styles.groupInfoSection}>
|
|
|
|
|
|
<Avatar
|
|
|
|
|
|
source={groupInfo?.avatar}
|
|
|
|
|
|
size={80}
|
|
|
|
|
|
name={groupInfo?.name || '群聊'}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Text style={styles.groupName}>{groupInfo?.name || '群聊'}</Text>
|
|
|
|
|
|
<Text style={styles.memberCount}>
|
|
|
|
|
|
{groupInfo?.member_count || members.length || 0} 位成员
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
{groupInfo?.join_type !== undefined && (
|
|
|
|
|
|
<Text style={styles.joinType}>
|
|
|
|
|
|
{getJoinTypeText(groupInfo.join_type)}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* 邀请新成员按钮 */}
|
|
|
|
|
|
{onInviteMembers && (
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={styles.inviteButton}
|
|
|
|
|
|
onPress={onInviteMembers}
|
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
|
>
|
|
|
|
|
|
<MaterialCommunityIcons name="account-plus" size={18} color={colors.primary.main} />
|
|
|
|
|
|
<Text style={styles.inviteButtonText}>邀请新成员</Text>
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 分割线 */}
|
|
|
|
|
|
<View style={styles.divider} />
|
|
|
|
|
|
|
|
|
|
|
|
{/* 群公告 */}
|
|
|
|
|
|
{announcements.length > 0 && (
|
|
|
|
|
|
<View style={styles.section}>
|
|
|
|
|
|
<View style={styles.sectionHeader}>
|
|
|
|
|
|
<MaterialCommunityIcons name="bullhorn" size={18} color={colors.warning.main} />
|
|
|
|
|
|
<Text style={styles.sectionTitle}>群公告</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<View style={styles.noticeBox}>
|
|
|
|
|
|
<Text style={styles.noticeText}>{announcements[0].content}</Text>
|
|
|
|
|
|
<Text style={styles.noticeTime}>
|
|
|
|
|
|
{new Date(announcements[0].created_at).toLocaleDateString()}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 群简介 */}
|
|
|
|
|
|
{!!groupInfo?.description && (
|
|
|
|
|
|
<View style={styles.section}>
|
|
|
|
|
|
<Text style={styles.sectionTitle}>群简介</Text>
|
|
|
|
|
|
<View style={styles.descriptionBox}>
|
|
|
|
|
|
<Text style={styles.description}>{groupInfo.description}</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 群成员列表 */}
|
|
|
|
|
|
<View style={styles.section}>
|
|
|
|
|
|
<View style={styles.memberListHeader}>
|
|
|
|
|
|
<Text style={styles.sectionTitle}>成员列表 ({groupInfo?.member_count || members.length})</Text>
|
|
|
|
|
|
{onViewAllMembers && !showAllMembers && members.length >= 10 && (
|
|
|
|
|
|
<TouchableOpacity onPress={loadAllMembers} style={styles.viewAllButton} disabled={loadingMoreMembers}>
|
|
|
|
|
|
<Text style={styles.viewAllText}>
|
|
|
|
|
|
{loadingMoreMembers ? '加载中...' : '查看全部'}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
<MaterialCommunityIcons name="chevron-right" size={16} color={colors.primary.main} />
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{onViewAllMembers && showAllMembers && (
|
|
|
|
|
|
<TouchableOpacity onPress={() => setShowAllMembers(false)} style={styles.viewAllButton}>
|
|
|
|
|
|
<Text style={styles.viewAllText}>收起</Text>
|
|
|
|
|
|
<MaterialCommunityIcons name="chevron-up" size={16} color={colors.primary.main} />
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<View style={styles.memberList}>
|
|
|
|
|
|
{(showAllMembers ? allMembers : members).map((member) => (
|
|
|
|
|
|
<View key={member.id || member.user_id} style={styles.memberItem}>
|
|
|
|
|
|
<Avatar
|
|
|
|
|
|
source={member.user?.avatar}
|
|
|
|
|
|
size={44}
|
|
|
|
|
|
name={member.nickname || member.user?.nickname || '用户'}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<View style={styles.memberInfo}>
|
|
|
|
|
|
<Text style={styles.memberName} numberOfLines={1}>
|
|
|
|
|
|
{member.nickname || member.user?.nickname || '用户'}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
{member.role === 'owner' && (
|
|
|
|
|
|
<Text style={styles.ownerBadge}>群主</Text>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{member.role === 'admin' && (
|
|
|
|
|
|
<Text style={styles.adminBadge}>管理员</Text>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 群信息 */}
|
|
|
|
|
|
<View style={styles.section}>
|
|
|
|
|
|
<Text style={styles.sectionTitle}>群信息</Text>
|
|
|
|
|
|
<View style={styles.infoRow}>
|
|
|
|
|
|
<Text style={styles.infoLabel}>群号</Text>
|
|
|
|
|
|
<Text style={styles.infoValue}>{groupInfo?.id || '-'}</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<View style={styles.infoRow}>
|
|
|
|
|
|
<Text style={styles.infoLabel}>创建时间</Text>
|
|
|
|
|
|
<Text style={styles.infoValue}>
|
|
|
|
|
|
{groupInfo?.created_at
|
|
|
|
|
|
? new Date(groupInfo.created_at).toLocaleDateString('zh-CN')
|
|
|
|
|
|
: '-'}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<View style={styles.infoRow}>
|
|
|
|
|
|
<Text style={styles.infoLabel}>全员禁言</Text>
|
|
|
|
|
|
<Text style={styles.infoValue}>
|
|
|
|
|
|
{groupInfo?.mute_all ? '已开启' : '已关闭'}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 操作按钮 */}
|
|
|
|
|
|
<View style={styles.actionSection}>
|
|
|
|
|
|
{conversationId && onTogglePin && (
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={styles.actionButton}
|
|
|
|
|
|
onPress={() => onTogglePin(!isPinned)}
|
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
|
>
|
|
|
|
|
|
<MaterialCommunityIcons
|
|
|
|
|
|
name={isPinned ? "pin-off" : "pin"}
|
|
|
|
|
|
size={22}
|
|
|
|
|
|
color={colors.primary.main}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Text style={styles.actionButtonText}>
|
|
|
|
|
|
{isPinned ? '取消置顶' : '置顶群聊'}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{onLeaveGroup && (
|
|
|
|
|
|
<TouchableOpacity
|
|
|
|
|
|
style={[styles.actionButton, styles.leaveButton]}
|
|
|
|
|
|
onPress={onLeaveGroup}
|
|
|
|
|
|
activeOpacity={0.7}
|
|
|
|
|
|
>
|
|
|
|
|
|
<MaterialCommunityIcons
|
|
|
|
|
|
name="logout"
|
|
|
|
|
|
size={22}
|
|
|
|
|
|
color={colors.error.main}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Text style={[styles.actionButtonText, styles.leaveButtonText]}>
|
|
|
|
|
|
退出群聊
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</TouchableOpacity>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</Animated.View>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function createGroupInfoPanelStyles(colors: AppColors) {
|
|
|
|
|
|
return StyleSheet.create({
|
|
|
|
|
|
overlay: {
|
|
|
|
|
|
...StyleSheet.absoluteFillObject,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.35)',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
zIndex: 100,
|
|
|
|
|
|
},
|
|
|
|
|
|
overlayTouchable: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
panel: {
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
right: 0,
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
bottom: 0,
|
|
|
|
|
|
width: PANEL_WIDTH,
|
|
|
|
|
|
backgroundColor: colors.background.paper,
|
|
|
|
|
|
zIndex: 101,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderLeftWidth: 0.5,
|
|
|
|
|
|
borderLeftColor: colors.divider + '60',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
header: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'space-between',
|
2026-03-30 17:53:30 +08:00
|
|
|
|
paddingHorizontal: spacing.lg,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
paddingVertical: spacing.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderBottomWidth: 0.5,
|
|
|
|
|
|
borderBottomColor: colors.divider + '60',
|
|
|
|
|
|
backgroundColor: colors.background.paper,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
headerTitle: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontSize: fontSizes.xl + 1,
|
|
|
|
|
|
fontWeight: '800',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
color: colors.text.primary,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
letterSpacing: 0.5,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
closeButton: {
|
|
|
|
|
|
padding: spacing.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderRadius: borderRadius.md,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
content: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
groupInfoSection: {
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
paddingVertical: spacing.xl,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
paddingHorizontal: spacing.md,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
groupName: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontSize: fontSizes.xl + 1,
|
|
|
|
|
|
fontWeight: '800',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
marginTop: spacing.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
letterSpacing: 0.3,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
memberCount: {
|
|
|
|
|
|
fontSize: fontSizes.md,
|
|
|
|
|
|
color: colors.text.secondary,
|
|
|
|
|
|
marginTop: spacing.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '500',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
joinType: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.primary.main,
|
|
|
|
|
|
marginTop: spacing.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '600',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
inviteButton: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
paddingVertical: spacing.sm,
|
|
|
|
|
|
paddingHorizontal: spacing.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: colors.primary.light + '12',
|
|
|
|
|
|
borderRadius: borderRadius.lg,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
marginTop: spacing.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderWidth: 1.5,
|
|
|
|
|
|
borderColor: colors.primary.light + '60',
|
|
|
|
|
|
borderStyle: 'dashed',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
inviteButtonText: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.primary.main,
|
|
|
|
|
|
marginLeft: spacing.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '700',
|
|
|
|
|
|
letterSpacing: 0.3,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
divider: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
height: 0.5,
|
|
|
|
|
|
backgroundColor: colors.divider + '60',
|
|
|
|
|
|
marginHorizontal: spacing.lg,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
section: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
padding: spacing.lg,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
sectionHeader: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
marginBottom: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
sectionTitle: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontSize: fontSizes.md + 1,
|
|
|
|
|
|
fontWeight: '700',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
marginLeft: spacing.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
letterSpacing: 0.3,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
noticeBox: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: colors.warning.light + '08',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
padding: spacing.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderRadius: borderRadius.lg,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
borderLeftWidth: 3,
|
|
|
|
|
|
borderLeftColor: colors.warning.main,
|
|
|
|
|
|
},
|
|
|
|
|
|
noticeText: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.text.secondary,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
lineHeight: 22,
|
|
|
|
|
|
fontWeight: '400',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
noticeTime: {
|
|
|
|
|
|
fontSize: fontSizes.xs,
|
|
|
|
|
|
color: colors.text.hint,
|
|
|
|
|
|
marginTop: spacing.xs,
|
|
|
|
|
|
textAlign: 'right',
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '500',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
descriptionBox: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: colors.background.default,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
padding: spacing.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderRadius: borderRadius.lg,
|
|
|
|
|
|
borderWidth: 0.5,
|
|
|
|
|
|
borderColor: colors.divider + '30',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
description: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.text.secondary,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
lineHeight: 22,
|
|
|
|
|
|
fontWeight: '400',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
memberList: {
|
|
|
|
|
|
gap: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
memberListHeader: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
marginBottom: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
viewAllButton: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
},
|
|
|
|
|
|
viewAllText: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.primary.main,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '600',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
memberItem: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
2026-03-30 17:53:30 +08:00
|
|
|
|
paddingVertical: spacing.sm,
|
|
|
|
|
|
paddingHorizontal: spacing.sm,
|
|
|
|
|
|
borderRadius: borderRadius.md,
|
|
|
|
|
|
marginLeft: -spacing.sm,
|
|
|
|
|
|
marginRight: -spacing.sm,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
memberInfo: {
|
|
|
|
|
|
flex: 1,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
marginLeft: spacing.md,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
},
|
|
|
|
|
|
memberName: {
|
|
|
|
|
|
fontSize: fontSizes.md,
|
|
|
|
|
|
color: colors.text.primary,
|
|
|
|
|
|
flex: 1,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '600',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
ownerBadge: {
|
|
|
|
|
|
fontSize: fontSizes.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
color: colors.background.paper,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
marginLeft: spacing.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: colors.warning.main,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
paddingHorizontal: 6,
|
|
|
|
|
|
paddingVertical: 2,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderRadius: borderRadius.sm,
|
|
|
|
|
|
fontWeight: '800',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
adminBadge: {
|
|
|
|
|
|
fontSize: fontSizes.xs,
|
|
|
|
|
|
color: colors.primary.main,
|
|
|
|
|
|
marginLeft: spacing.xs,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: colors.primary.light + '20',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
paddingHorizontal: 6,
|
|
|
|
|
|
paddingVertical: 2,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderRadius: borderRadius.sm,
|
|
|
|
|
|
fontWeight: '700',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
moreMembers: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.text.secondary,
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
paddingVertical: spacing.sm,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '500',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
infoRow: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
justifyContent: 'space-between',
|
2026-03-30 17:53:30 +08:00
|
|
|
|
paddingVertical: spacing.md,
|
|
|
|
|
|
borderBottomWidth: 0.5,
|
|
|
|
|
|
borderBottomColor: colors.divider + '40',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
infoLabel: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.text.secondary,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '500',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
infoValue: {
|
|
|
|
|
|
fontSize: fontSizes.sm,
|
|
|
|
|
|
color: colors.text.primary,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '600',
|
|
|
|
|
|
textAlign: 'right',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
actionSection: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
padding: spacing.lg,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
paddingBottom: spacing.xl,
|
|
|
|
|
|
},
|
|
|
|
|
|
actionButton: {
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
paddingVertical: spacing.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: colors.primary.light + '10',
|
|
|
|
|
|
borderRadius: borderRadius.lg,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
marginBottom: spacing.sm,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
borderWidth: 1.5,
|
|
|
|
|
|
borderColor: colors.primary.light + '50',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
actionButtonText: {
|
|
|
|
|
|
fontSize: fontSizes.md,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '700',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
color: colors.primary.main,
|
|
|
|
|
|
marginLeft: spacing.sm,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
letterSpacing: 0.3,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
leaveButton: {
|
2026-03-30 17:53:30 +08:00
|
|
|
|
backgroundColor: colors.error.light + '08',
|
|
|
|
|
|
borderColor: colors.error.light + '50',
|
|
|
|
|
|
borderWidth: 1.5,
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
leaveButtonText: {
|
|
|
|
|
|
color: colors.error.main,
|
2026-03-30 17:53:30 +08:00
|
|
|
|
fontWeight: '700',
|
2026-03-29 02:34:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default GroupInfoPanel;
|