/** * SystemMessageItem 系统消息项组件 - 美化版 * 根据系统消息类型显示不同图标和内容 * 采用卡片式设计,符合胡萝卜BBS整体风格 */ import React, { useMemo } from 'react'; import { View, TouchableOpacity, StyleSheet } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { formatDistanceToNow } from 'date-fns'; import { zhCN } from 'date-fns/locale'; import { spacing, borderRadius, shadows, fontSizes, useAppColors, type AppColors } from '../../theme'; import { SystemMessageResponse, SystemMessageType } from '../../types/dto'; import Text from '../common/Text'; import Avatar from '../common/Avatar'; interface SystemMessageItemProps { message: SystemMessageResponse; onPress?: () => void; onAvatarPress?: () => void; onRequestAction?: (approve: boolean) => void; requestActionLoading?: boolean; index?: number; // 用于交错动画 } const getSystemMessageIcon = ( systemType: SystemMessageType, colors: AppColors ): { icon: keyof typeof MaterialCommunityIcons.glyphMap; color: string; bgColor: string; gradient: string[] } => { switch (systemType) { case 'like_post': case 'like_comment': case 'like_reply': case 'favorite_post': return { icon: 'heart', color: '#FF4757', bgColor: '#FFE4E6', gradient: ['#FF4757', '#FF6B7A'], }; case 'comment': return { icon: 'comment-text', color: '#2196F3', bgColor: '#E3F2FD', gradient: ['#2196F3', '#64B5F6'], }; case 'reply': return { icon: 'reply', color: '#4CAF50', bgColor: '#E8F5E9', gradient: ['#4CAF50', '#81C784'], }; case 'follow': return { icon: 'account-plus', color: '#9C27B0', bgColor: '#F3E5F5', gradient: ['#9C27B0', '#BA68C8'], }; case 'mention': return { icon: 'at', color: '#FF9800', bgColor: '#FFF3E0', gradient: ['#FF9800', '#FFB74D'], }; case 'system': return { icon: 'cog', color: '#607D8B', bgColor: '#ECEFF1', gradient: ['#607D8B', '#90A4AE'], }; case 'announcement': case 'announce': return { icon: 'bullhorn', color: colors.primary.main, bgColor: colors.primary.light + '30', gradient: [colors.primary.main, colors.primary.light], }; case 'group_invite': return { icon: 'account-multiple-plus', color: '#00BCD4', bgColor: '#E0F7FA', gradient: ['#00BCD4', '#4DD0E1'], }; case 'group_join_apply': return { icon: 'account-clock', color: '#FF9800', bgColor: '#FFF3E0', gradient: ['#FF9800', '#FFB74D'], }; case 'group_join_approved': return { icon: 'check-circle', color: '#4CAF50', bgColor: '#E8F5E9', gradient: ['#4CAF50', '#81C784'], }; case 'group_join_rejected': return { icon: 'close-circle', color: '#F44336', bgColor: '#FFEBEE', gradient: ['#F44336', '#E57373'], }; default: return { icon: 'bell', color: '#757575', bgColor: '#F5F5F5', gradient: ['#757575', '#9E9E9E'], }; } }; function createSystemMessageStyles(colors: AppColors) { return StyleSheet.create({ container: { flexDirection: 'row', alignItems: 'flex-start', padding: spacing.md, backgroundColor: colors.background.paper, borderRadius: borderRadius.lg, marginHorizontal: spacing.md, marginBottom: spacing.sm, ...shadows.sm, }, unreadContainer: { backgroundColor: colors.primary.light + '08', borderLeftWidth: 3, borderLeftColor: colors.primary.main, }, unreadIndicator: { position: 'absolute', left: 6, top: '50%', marginTop: -4, width: 8, height: 8, borderRadius: borderRadius.full, backgroundColor: colors.primary.main, }, iconContainer: { marginRight: spacing.md, }, avatarWrapper: { position: 'relative', }, iconWrapper: { width: 48, height: 48, borderRadius: borderRadius.lg, justifyContent: 'center', alignItems: 'center', }, typeIconBadge: { position: 'absolute', bottom: -2, right: -2, width: 20, height: 20, borderRadius: borderRadius.full, justifyContent: 'center', alignItems: 'center', borderWidth: 2, borderColor: colors.background.paper, }, content: { flex: 1, justifyContent: 'center', minWidth: 0, }, titleRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: spacing.xs, }, titleLeft: { flexDirection: 'row', alignItems: 'center', flex: 1, marginRight: spacing.sm, }, title: { fontWeight: '500', fontSize: fontSizes.md, color: colors.text.primary, }, unreadTitle: { fontWeight: '600', color: colors.text.primary, }, statusBadge: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: spacing.xs, paddingVertical: 2, borderRadius: borderRadius.sm, marginLeft: spacing.xs, }, statusText: { fontSize: 10, fontWeight: '500', marginLeft: 2, }, time: { fontSize: fontSizes.sm, flexShrink: 0, }, messageContent: { lineHeight: 20, fontSize: fontSizes.sm, }, extraInfo: { flexDirection: 'row', alignItems: 'center', marginTop: spacing.xs, backgroundColor: colors.primary.light + '12', paddingHorizontal: spacing.sm, paddingVertical: spacing.xs, borderRadius: borderRadius.md, alignSelf: 'flex-start', }, extraText: { marginLeft: spacing.xs, flex: 1, fontWeight: '500', }, actionsRow: { flexDirection: 'row', marginTop: spacing.sm, gap: spacing.sm, }, actionBtn: { flexDirection: 'row', alignItems: 'center', paddingVertical: spacing.xs, paddingHorizontal: spacing.md, borderRadius: borderRadius.md, borderWidth: 1, gap: spacing.xs, }, rejectBtn: { borderColor: `${colors.error.main}55`, backgroundColor: `${colors.error.main}18`, }, approveBtn: { borderColor: `${colors.success.main}55`, backgroundColor: `${colors.success.main}18`, }, actionBtnText: { fontWeight: '500', }, arrowContainer: { marginLeft: spacing.sm, justifyContent: 'center', alignItems: 'center', height: 20, width: 20, }, }); } // 获取系统消息标题 const getSystemMessageTitle = (message: SystemMessageResponse): string => { const { system_type, extra_data } = message; const operatorName = extra_data?.actor_name || extra_data?.operator_name; const groupName = extra_data?.group_name || extra_data?.target_title; switch (system_type) { case 'like_post': return operatorName ? `${operatorName} 赞了你的帖子` : '有人赞了你的帖子'; case 'like_comment': return operatorName ? `${operatorName} 赞了你的评论` : '有人赞了你的评论'; case 'like_reply': return operatorName ? `${operatorName} 赞了你的回复` : '有人赞了你的回复'; case 'favorite_post': return operatorName ? `${operatorName} 收藏了你的帖子` : '有人收藏了你的帖子'; case 'comment': return operatorName ? `${operatorName} 评论了你的帖子` : '有人评论了你的帖子'; case 'reply': return operatorName ? `${operatorName} 回复了你` : '有人回复了你'; case 'follow': return operatorName ? `${operatorName} 关注了你` : '有人关注了你'; case 'mention': return operatorName ? `${operatorName} @提到了你` : '有人@提到了你'; case 'system': return '系统通知'; case 'announcement': return '公告'; case 'group_invite': return operatorName ? `${operatorName} 邀请加入群聊 ${groupName || ''}`.trim() : `收到群邀请${groupName ? `:${groupName}` : ''}`; case 'group_join_apply': if (extra_data?.request_status === 'accepted') { return operatorName ? `${operatorName} 已同意` : '该请求已同意'; } if (extra_data?.request_status === 'rejected') { return operatorName ? `${operatorName} 已拒绝` : '该请求已拒绝'; } return operatorName ? `${operatorName} 申请加入群聊 ${groupName || ''}`.trim() : `收到加群申请${groupName ? `:${groupName}` : ''}`; case 'group_join_approved': return '加群申请已通过'; case 'group_join_rejected': return '加群申请被拒绝'; default: return '通知'; } }; const SystemMessageItem: React.FC = ({ message, onPress, onAvatarPress, onRequestAction, requestActionLoading = false, index = 0, }) => { const colors = useAppColors(); const styles = useMemo(() => createSystemMessageStyles(colors), [colors]); // 格式化时间 const formatTime = (dateString: string): string => { try { return formatDistanceToNow(new Date(dateString), { addSuffix: true, locale: zhCN, }); } catch { return ''; } }; const { icon, color, bgColor } = getSystemMessageIcon(message.system_type, colors); const title = getSystemMessageTitle(message); const { extra_data } = message; const operatorName = extra_data?.actor_name || extra_data?.operator_name; const operatorAvatar = extra_data?.avatar_url || extra_data?.operator_avatar; const groupAvatar = extra_data?.group_avatar; const requestStatus = extra_data?.request_status; const isActionable = (message.system_type === 'group_invite' || message.system_type === 'group_join_apply') && requestStatus === 'pending' && !!onRequestAction; const isUnread = message.is_read !== true; // 判断是否显示操作者头像 const showOperatorAvatar = ['follow', 'like_post', 'like_comment', 'like_reply', 'favorite_post', 'comment', 'reply', 'mention', 'group_join_apply'].includes( message.system_type ); const showGroupAvatar = message.system_type === 'group_invite'; // 获取状态标签 const getStatusBadge = () => { if (requestStatus === 'accepted') { return ( 已同意 ); } if (requestStatus === 'rejected') { return ( 已拒绝 ); } return null; }; return ( {/* 左侧未读指示器 */} {isUnread && } {/* 图标/头像区域 */} {showGroupAvatar ? ( ) : showOperatorAvatar ? ( ) : ( )} {/* 内容区域 */} {/* 标题行 */} {title} {getStatusBadge()} {formatTime(message.created_at)} {/* 消息内容 */} {message.content} {/* 附加信息 */} {(extra_data?.target_title || extra_data?.post_title || extra_data?.comment_preview) && !['group_invite', 'group_join_apply', 'group_join_approved', 'group_join_rejected'].includes(message.system_type) && (() => { const isCommentType = ['comment', 'reply'].includes(extra_data?.target_type ?? ''); const previewText = extra_data?.target_title || extra_data?.post_title || extra_data?.comment_preview; const iconName = isCommentType ? 'comment-text-outline' : 'file-document-outline'; return ( {previewText} ); })()} {/* 操作按钮 */} {isActionable && ( onRequestAction?.(false)} disabled={requestActionLoading} > 拒绝 onRequestAction?.(true)} disabled={requestActionLoading} > 同意 )} {/* 右侧箭头 */} {onPress && ( )} ); }; export default SystemMessageItem;