Add comprehensive date validation using Number.isNaN() checks to prevent crashes when timestamps are invalid or empty. Apply defensive formatting across CommentItem, SystemMessageItem, MessageBubble, LongPressMenu, and store utilities. Refine message bubble styling: move sender name display to both sides in group chat with distinct `mySenderName` styling, adjust bubble corner radius to top-right for consistent arrow placement, and align message rows to flex-start for improved layout. Simplify group avatar rendering in ConversationListRow.
703 lines
21 KiB
TypeScript
703 lines
21 KiB
TypeScript
/**
|
||
* CommentItem 评论项组件 - 小红书 / 贴吧式平铺列表
|
||
* 支持嵌套回复显示、楼层号、身份标识、删除评论、图片显示
|
||
*/
|
||
|
||
import React, { useMemo, useState } from 'react';
|
||
import { View, TouchableOpacity, StyleSheet, Alert } from 'react-native';
|
||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||
import { formatDistanceToNow } from 'date-fns';
|
||
import { zhCN } from 'date-fns/locale';
|
||
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
|
||
import { Comment, CommentImage } from '../../types';
|
||
import Text from '../common/Text';
|
||
import Avatar from '../common/Avatar';
|
||
import { CompactImageGrid, ImageGridItem } from '../common';
|
||
import PostContentRenderer from './PostContentRenderer';
|
||
|
||
interface CommentItemProps {
|
||
comment: Comment;
|
||
onUserPress: () => void;
|
||
onReply: () => void;
|
||
onLike: (comment: Comment) => void; // 点赞回调,传入评论对象
|
||
floorNumber?: number; // 楼层号
|
||
isAuthor?: boolean; // 是否是楼主
|
||
replyToUser?: string; // 回复给哪位用户
|
||
onReplyPress?: (comment: Comment) => void; // 点击评论的评论
|
||
allReplies?: Comment[]; // 所有回复列表,用于根据 target_id 查找被回复用户
|
||
onLoadMoreReplies?: (commentId: string) => void; // 加载更多回复的回调
|
||
isCommentAuthor?: boolean; // 当前用户是否为评论作者
|
||
onDelete?: (comment: Comment) => void; // 删除评论的回调
|
||
onImagePress?: (images: ImageGridItem[], index: number) => void; // 点击图片查看大图
|
||
currentUserId?: string; // 当前用户ID,用于判断子评论作者
|
||
onReport?: (comment: Comment) => void; // 举报评论的回调
|
||
}
|
||
|
||
function createCommentItemStyles(colors: AppColors) {
|
||
return StyleSheet.create({
|
||
container: {
|
||
flexDirection: 'row',
|
||
paddingTop: spacing.md,
|
||
paddingBottom: spacing.md,
|
||
paddingHorizontal: spacing.md,
|
||
backgroundColor: colors.background.paper,
|
||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||
borderBottomColor: colors.divider,
|
||
},
|
||
content: {
|
||
flex: 1,
|
||
marginLeft: spacing.sm,
|
||
minWidth: 0,
|
||
},
|
||
header: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
marginBottom: spacing.xs,
|
||
gap: spacing.xs,
|
||
},
|
||
userInfo: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
flexWrap: 'wrap',
|
||
flex: 1,
|
||
minWidth: 0,
|
||
},
|
||
metaDot: {
|
||
fontSize: fontSizes.xs,
|
||
color: colors.text.hint,
|
||
marginHorizontal: 2,
|
||
},
|
||
username: {
|
||
fontWeight: '600',
|
||
fontSize: fontSizes.sm,
|
||
color: colors.text.primary,
|
||
marginRight: spacing.xs,
|
||
},
|
||
badge: {
|
||
paddingHorizontal: 4,
|
||
paddingVertical: 1,
|
||
borderRadius: 2,
|
||
marginRight: spacing.xs,
|
||
},
|
||
smallBadge: {
|
||
paddingHorizontal: 2,
|
||
paddingVertical: 0,
|
||
},
|
||
authorBadge: {
|
||
backgroundColor: colors.primary.main,
|
||
},
|
||
adminBadge: {
|
||
backgroundColor: colors.error.main,
|
||
},
|
||
badgeText: {
|
||
color: colors.text.inverse,
|
||
fontSize: fontSizes.xs,
|
||
fontWeight: '600',
|
||
},
|
||
smallBadgeText: {
|
||
color: colors.text.inverse,
|
||
fontSize: 9,
|
||
fontWeight: '600',
|
||
},
|
||
timeText: {
|
||
fontSize: fontSizes.xs,
|
||
flexShrink: 0,
|
||
},
|
||
floorPlain: {
|
||
fontSize: fontSizes.xs,
|
||
flexShrink: 0,
|
||
},
|
||
replyReference: {
|
||
marginBottom: spacing.xs,
|
||
},
|
||
commentContent: {
|
||
marginBottom: spacing.xs,
|
||
},
|
||
text: {
|
||
lineHeight: 22,
|
||
fontSize: fontSizes.md,
|
||
color: colors.text.primary,
|
||
},
|
||
actions: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginTop: spacing.xs,
|
||
flexWrap: 'wrap',
|
||
},
|
||
actionButton: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginRight: spacing.lg,
|
||
paddingVertical: 4,
|
||
paddingRight: spacing.xs,
|
||
},
|
||
actionText: {
|
||
marginLeft: 4,
|
||
fontSize: fontSizes.xs,
|
||
},
|
||
subRepliesContainer: {
|
||
marginTop: spacing.sm,
|
||
marginLeft: 0,
|
||
paddingLeft: spacing.sm,
|
||
paddingVertical: spacing.xs,
|
||
borderLeftWidth: 2,
|
||
borderLeftColor: colors.divider,
|
||
backgroundColor: colors.background.default,
|
||
},
|
||
subReplyItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'flex-start',
|
||
marginBottom: spacing.md,
|
||
},
|
||
subReplyItemLast: {
|
||
marginBottom: 0,
|
||
},
|
||
subReplyBody: {
|
||
fontSize: fontSizes.sm,
|
||
lineHeight: 20,
|
||
marginTop: 2,
|
||
},
|
||
subReplyContent: {
|
||
flex: 1,
|
||
marginLeft: spacing.xs,
|
||
},
|
||
subReplyHeader: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginBottom: 2,
|
||
},
|
||
subReplyAuthor: {
|
||
fontWeight: '600',
|
||
color: colors.text.primary,
|
||
marginRight: spacing.xs,
|
||
},
|
||
moreRepliesButton: {
|
||
marginTop: spacing.xs,
|
||
paddingVertical: spacing.xs,
|
||
},
|
||
replyToText: {
|
||
fontSize: fontSizes.xs,
|
||
},
|
||
replyToName: {
|
||
fontSize: fontSizes.xs,
|
||
fontWeight: '500',
|
||
},
|
||
subReplyActions: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginTop: spacing.xs,
|
||
},
|
||
subActionButton: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginRight: spacing.sm,
|
||
paddingVertical: 2,
|
||
},
|
||
subActionText: {
|
||
marginLeft: 2,
|
||
fontSize: fontSizes.xs,
|
||
},
|
||
});
|
||
}
|
||
|
||
const CommentItem: React.FC<CommentItemProps> = ({
|
||
comment,
|
||
onUserPress,
|
||
onReply,
|
||
onLike,
|
||
floorNumber,
|
||
isAuthor = false,
|
||
replyToUser,
|
||
onReplyPress,
|
||
allReplies,
|
||
onLoadMoreReplies,
|
||
isCommentAuthor = false,
|
||
onDelete,
|
||
onImagePress,
|
||
currentUserId,
|
||
onReport,
|
||
}) => {
|
||
const colors = useAppColors();
|
||
const styles = useMemo(() => createCommentItemStyles(colors), [colors]);
|
||
const [isDeleting, setIsDeleting] = useState(false);
|
||
// 格式化时间
|
||
const formatTime = (dateString: string): string => {
|
||
if (!dateString) return '';
|
||
try {
|
||
const date = new Date(dateString);
|
||
if (Number.isNaN(date.getTime())) return '';
|
||
return formatDistanceToNow(date, {
|
||
addSuffix: true,
|
||
locale: zhCN,
|
||
});
|
||
} catch {
|
||
return '';
|
||
}
|
||
};
|
||
|
||
// 格式化数字
|
||
const formatNumber = (num: number): string => {
|
||
if (num >= 10000) {
|
||
return (num / 10000).toFixed(1) + 'w';
|
||
}
|
||
if (num >= 1000) {
|
||
return (num / 1000).toFixed(1) + 'k';
|
||
}
|
||
return num.toString();
|
||
};
|
||
|
||
// 处理图片点击
|
||
const handleImagePress = (index: number) => {
|
||
if (onImagePress && comment.images && comment.images.length > 0) {
|
||
const images: ImageGridItem[] = comment.images.map(img => ({
|
||
id: img.url,
|
||
url: img.url,
|
||
}));
|
||
onImagePress(images, index);
|
||
}
|
||
};
|
||
|
||
// 渲染评论图片 - 使用 CompactImageGrid
|
||
const renderCommentImages = () => {
|
||
if (!comment.images || comment.images.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
const gridImages: ImageGridItem[] = comment.images.map(img => ({
|
||
id: img.url,
|
||
url: img.url,
|
||
}));
|
||
|
||
return (
|
||
<CompactImageGrid
|
||
images={gridImages}
|
||
maxDisplayCount={6}
|
||
gap={4}
|
||
borderRadius={borderRadius.sm}
|
||
showMoreOverlay
|
||
onImagePress={onImagePress}
|
||
/>
|
||
);
|
||
};
|
||
|
||
// 处理删除评论
|
||
const handleDelete = () => {
|
||
if (!onDelete || isDeleting) return;
|
||
|
||
Alert.alert(
|
||
'删除评论',
|
||
'确定要删除这条评论吗?删除后将无法恢复。',
|
||
[
|
||
{
|
||
text: '取消',
|
||
style: 'cancel',
|
||
},
|
||
{
|
||
text: '删除',
|
||
style: 'destructive',
|
||
onPress: async () => {
|
||
setIsDeleting(true);
|
||
try {
|
||
await onDelete(comment);
|
||
} catch (error) {
|
||
console.error('删除评论失败:', error);
|
||
Alert.alert('删除失败', '删除评论时发生错误,请稍后重试');
|
||
} finally {
|
||
setIsDeleting(false);
|
||
}
|
||
},
|
||
},
|
||
],
|
||
);
|
||
};
|
||
|
||
// 渲染身份标识
|
||
const renderBadges = () => {
|
||
const badges = [];
|
||
const authorId = comment.author?.id || '';
|
||
|
||
if (isAuthor) {
|
||
badges.push(
|
||
<View key="author" style={[styles.badge, styles.authorBadge]}>
|
||
<Text variant="caption" style={styles.badgeText}>楼主</Text>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
if (authorId === '1') { // 管理员
|
||
badges.push(
|
||
<View key="admin" style={[styles.badge, styles.adminBadge]}>
|
||
<Text variant="caption" style={styles.badgeText}>管理员</Text>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
return badges;
|
||
};
|
||
|
||
// 渲染楼层号
|
||
const renderFloorNumber = () => {
|
||
if (!floorNumber) return null;
|
||
|
||
// 获取楼层显示文本
|
||
const getFloorText = (floor: number): string => {
|
||
switch (floor) {
|
||
case 1:
|
||
return '沙发';
|
||
case 2:
|
||
return '板凳';
|
||
case 3:
|
||
return '地板';
|
||
default:
|
||
return `${floor}楼`;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Text variant="caption" color={colors.text.hint} style={styles.floorPlain}>
|
||
{getFloorText(floorNumber)}
|
||
</Text>
|
||
);
|
||
};
|
||
|
||
// 根据 target_id 查找被回复用户的昵称
|
||
const getTargetUserNickname = (targetId: string): string => {
|
||
// 首先在顶级评论中查找
|
||
if (comment.id === targetId) {
|
||
return comment.author?.nickname || '用户';
|
||
}
|
||
// 然后在回复列表中查找
|
||
if (allReplies) {
|
||
const targetComment = allReplies.find(r => r.id === targetId);
|
||
if (targetComment) {
|
||
return targetComment.author?.nickname || '用户';
|
||
}
|
||
}
|
||
// 最后在当前评论的 replies 中查找
|
||
if (comment.replies) {
|
||
const targetComment = comment.replies.find(r => r.id === targetId);
|
||
if (targetComment) {
|
||
return targetComment.author?.nickname || '用户';
|
||
}
|
||
}
|
||
return '用户';
|
||
};
|
||
|
||
// 渲染子评论图片
|
||
const renderSubReplyImages = (reply: Comment) => {
|
||
if (!reply.images || reply.images.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
const gridImages: ImageGridItem[] = reply.images.map(img => ({
|
||
id: img.url,
|
||
url: img.url,
|
||
}));
|
||
|
||
return (
|
||
<CompactImageGrid
|
||
images={gridImages}
|
||
maxDisplayCount={3}
|
||
gap={4}
|
||
borderRadius={borderRadius.sm}
|
||
showMoreOverlay
|
||
onImagePress={onImagePress}
|
||
/>
|
||
);
|
||
};
|
||
|
||
// 处理子评论删除
|
||
const handleSubReplyDelete = (reply: Comment) => {
|
||
if (!onDelete || isDeleting) return;
|
||
|
||
Alert.alert(
|
||
'删除回复',
|
||
'确定要删除这条回复吗?删除后将无法恢复。',
|
||
[
|
||
{
|
||
text: '取消',
|
||
style: 'cancel',
|
||
},
|
||
{
|
||
text: '删除',
|
||
style: 'destructive',
|
||
onPress: async () => {
|
||
setIsDeleting(true);
|
||
try {
|
||
await onDelete(reply);
|
||
} catch (error) {
|
||
console.error('删除回复失败:', error);
|
||
Alert.alert('删除失败', '删除回复时发生错误,请稍后重试');
|
||
} finally {
|
||
setIsDeleting(false);
|
||
}
|
||
},
|
||
},
|
||
],
|
||
);
|
||
};
|
||
|
||
// 渲染评论的评论(子评论)- 抖音/b站风格:平铺展示
|
||
const renderSubReplies = () => {
|
||
if (!comment.replies || comment.replies.length === 0) {
|
||
return null;
|
||
}
|
||
|
||
const commentAuthorId = comment.author?.id || '';
|
||
|
||
return (
|
||
<View style={styles.subRepliesContainer}>
|
||
{comment.replies.map((reply, replyIndex) => {
|
||
const replyAuthorId = reply.author?.id || '';
|
||
const isLastReply = replyIndex === comment.replies!.length - 1;
|
||
// 根据 target_id 获取被回复的用户昵称
|
||
const targetId = reply.target_id;
|
||
const targetNickname = targetId ? getTargetUserNickname(targetId) : null;
|
||
// 判断当前用户是否为子评论作者
|
||
const isSubReplyAuthor = currentUserId === replyAuthorId;
|
||
|
||
return (
|
||
<TouchableOpacity
|
||
key={reply.id}
|
||
style={[styles.subReplyItem, isLastReply && styles.subReplyItemLast]}
|
||
onPress={() => onReplyPress?.(reply)}
|
||
activeOpacity={0.7}
|
||
>
|
||
<Avatar
|
||
source={reply.author?.avatar}
|
||
size={24}
|
||
name={reply.author?.nickname || '用户'}
|
||
/>
|
||
<View style={styles.subReplyContent}>
|
||
<View style={styles.subReplyHeader}>
|
||
<Text variant="caption" style={styles.subReplyAuthor}>
|
||
{reply.author?.nickname || '用户'}
|
||
</Text>
|
||
{replyAuthorId === commentAuthorId && (
|
||
<View style={[styles.badge, styles.authorBadge, styles.smallBadge]}>
|
||
<Text variant="caption" style={styles.smallBadgeText}>楼主</Text>
|
||
</View>
|
||
)}
|
||
{/* 显示回复引用:aaa 回复 bbb */}
|
||
{targetNickname && (
|
||
<>
|
||
<Text variant="caption" color={colors.text.hint} style={styles.replyToText}>
|
||
{' '}回复
|
||
</Text>
|
||
<Text variant="caption" color={colors.primary.main} style={styles.replyToName}>
|
||
{targetNickname}
|
||
</Text>
|
||
</>
|
||
)}
|
||
</View>
|
||
{/* 显示回复内容(如果有文字) */}
|
||
{reply.content ? (
|
||
<Text variant="body" color={colors.text.primary} style={styles.subReplyBody}>
|
||
{reply.content}
|
||
</Text>
|
||
) : null}
|
||
{/* 显示回复图片(如果有图片) */}
|
||
{renderSubReplyImages(reply)}
|
||
{/* 子评论操作按钮 */}
|
||
<View style={styles.subReplyActions}>
|
||
<TouchableOpacity
|
||
style={styles.subActionButton}
|
||
onPress={() => onReplyPress?.(reply)}
|
||
>
|
||
<MaterialCommunityIcons name="reply" size={12} color={colors.text.hint} />
|
||
<Text variant="caption" color={colors.text.hint} style={styles.subActionText}>
|
||
回复
|
||
</Text>
|
||
</TouchableOpacity>
|
||
{/* 点赞按钮 */}
|
||
<TouchableOpacity
|
||
style={styles.subActionButton}
|
||
onPress={() => onLike?.(reply)}
|
||
activeOpacity={0.7}
|
||
>
|
||
<MaterialCommunityIcons
|
||
name={reply.is_liked ? 'heart' : 'heart-outline'}
|
||
size={12}
|
||
color={reply.is_liked ? colors.error.main : colors.text.hint}
|
||
/>
|
||
<Text
|
||
variant="caption"
|
||
color={reply.is_liked ? colors.error.main : colors.text.hint}
|
||
style={styles.subActionText}
|
||
>
|
||
{reply.likes_count > 0 ? formatNumber(reply.likes_count) : '赞'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
{/* 删除按钮 - 子评论作者可见 */}
|
||
{isSubReplyAuthor && (
|
||
<TouchableOpacity
|
||
style={styles.subActionButton}
|
||
onPress={() => handleSubReplyDelete(reply)}
|
||
disabled={isDeleting}
|
||
>
|
||
<MaterialCommunityIcons
|
||
name={isDeleting ? 'loading' : 'delete-outline'}
|
||
size={12}
|
||
color={colors.text.hint}
|
||
/>
|
||
<Text variant="caption" color={colors.text.hint} style={styles.subActionText}>
|
||
{isDeleting ? '删除中' : '删除'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
{/* 举报按钮 - 对非子评论作者显示 */}
|
||
{!isSubReplyAuthor && onReport && (
|
||
<TouchableOpacity
|
||
style={styles.subActionButton}
|
||
onPress={() => onReport(reply)}
|
||
>
|
||
<MaterialCommunityIcons name="flag-outline" size={12} color={colors.text.hint} />
|
||
<Text variant="caption" color={colors.text.hint} style={styles.subActionText}>
|
||
举报
|
||
</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</TouchableOpacity>
|
||
);
|
||
})}
|
||
{comment.replies_count > (comment.replies?.length || 0) && (
|
||
<TouchableOpacity
|
||
style={styles.moreRepliesButton}
|
||
onPress={() => onLoadMoreReplies?.(comment.id)}
|
||
>
|
||
<Text variant="caption" color={colors.primary.main}>
|
||
展开剩余 {comment.replies_count - (comment.replies?.length || 0)} 条回复
|
||
</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
</View>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
{/* 用户头像 - 略大便于点击,与正文左对齐 */}
|
||
<TouchableOpacity onPress={onUserPress} activeOpacity={0.7}>
|
||
<Avatar
|
||
source={comment.author?.avatar}
|
||
size={40}
|
||
name={comment.author?.nickname}
|
||
/>
|
||
</TouchableOpacity>
|
||
|
||
{/* 评论内容 */}
|
||
<View style={styles.content}>
|
||
{/* 用户信息行 - QQ频道风格 */}
|
||
<View style={styles.header}>
|
||
<View style={styles.userInfo}>
|
||
<TouchableOpacity onPress={onUserPress} activeOpacity={0.7}>
|
||
<Text variant="body" style={styles.username} numberOfLines={1}>
|
||
{comment.author?.nickname || '用户'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
{renderBadges()}
|
||
<Text style={styles.metaDot}>·</Text>
|
||
<Text variant="caption" color={colors.text.hint} style={styles.timeText}>
|
||
{formatTime(comment.created_at || '')}
|
||
</Text>
|
||
</View>
|
||
{renderFloorNumber()}
|
||
</View>
|
||
|
||
{/* 回复引用 */}
|
||
{replyToUser && (
|
||
<View style={styles.replyReference}>
|
||
<Text variant="caption" color={colors.primary.main}>
|
||
回复 @{replyToUser}:
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
{/* 评论文本 - 非气泡样式 */}
|
||
<View style={styles.commentContent}>
|
||
{comment.segments && comment.segments.length > 0 ? (
|
||
<PostContentRenderer
|
||
content={comment.content}
|
||
segments={comment.segments}
|
||
textStyle={[styles.text, { color: colors.text.primary }]}
|
||
/>
|
||
) : (
|
||
<Text variant="body" color={colors.text.primary} style={styles.text}>
|
||
{comment.content}
|
||
</Text>
|
||
)}
|
||
</View>
|
||
|
||
{/* 评论图片 */}
|
||
{renderCommentImages()}
|
||
|
||
{/* 操作按钮 - 更紧凑 */}
|
||
<View style={styles.actions}>
|
||
{/* 点赞 */}
|
||
<TouchableOpacity style={styles.actionButton} onPress={() => onLike(comment)}>
|
||
<MaterialCommunityIcons
|
||
name={comment.is_liked ? 'heart' : 'heart-outline'}
|
||
size={14}
|
||
color={comment.is_liked ? colors.error.main : colors.text.hint}
|
||
/>
|
||
<Text
|
||
variant="caption"
|
||
color={comment.is_liked ? colors.error.main : colors.text.hint}
|
||
style={styles.actionText}
|
||
>
|
||
{comment.likes_count > 0 ? formatNumber(comment.likes_count) : '赞'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
|
||
{/* 回复 */}
|
||
<TouchableOpacity style={styles.actionButton} onPress={onReply}>
|
||
<MaterialCommunityIcons name="reply" size={14} color={colors.text.hint} />
|
||
<Text variant="caption" color={colors.text.hint} style={styles.actionText}>
|
||
回复
|
||
</Text>
|
||
</TouchableOpacity>
|
||
|
||
{/* 举报按钮 - 只对非评论作者显示 */}
|
||
{!isCommentAuthor && onReport && (
|
||
<TouchableOpacity
|
||
style={styles.actionButton}
|
||
onPress={() => onReport(comment)}
|
||
>
|
||
<MaterialCommunityIcons name="flag-outline" size={14} color={colors.text.hint} />
|
||
<Text variant="caption" color={colors.text.hint} style={styles.actionText}>
|
||
举报
|
||
</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
|
||
{/* 删除按钮 - 只对评论作者显示 */}
|
||
{isCommentAuthor && (
|
||
<TouchableOpacity
|
||
style={styles.actionButton}
|
||
onPress={handleDelete}
|
||
disabled={isDeleting}
|
||
>
|
||
<MaterialCommunityIcons
|
||
name={isDeleting ? 'loading' : 'delete-outline'}
|
||
size={14}
|
||
color={colors.text.hint}
|
||
/>
|
||
<Text variant="caption" color={colors.text.hint} style={styles.actionText}>
|
||
{isDeleting ? '删除中' : '删除'}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
)}
|
||
</View>
|
||
|
||
{/* 评论的评论(子评论)- 抖音/b站风格:平铺展示不开新层级 */}
|
||
{renderSubReplies()}
|
||
</View>
|
||
</View>
|
||
);
|
||
};
|
||
|
||
export default CommentItem;
|