/** * 举报对话框组件 * 支持举报帖子、评论、消息 * 提供友好的用户界面和流畅的交互体验 */ import React, { useState, useCallback, useMemo, useEffect } from 'react'; import { Modal, View, TouchableOpacity, StyleSheet, ScrollView, TextInput, ActivityIndicator, Alert, Platform, KeyboardAvoidingView, } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useAppColors } from '../../../theme'; import { spacing, borderRadius, fontSizes, shadows } from '../../../theme'; import { reportService, ReportReason, ReportTargetType, REPORT_REASONS } from '@/services/post'; import { blurActiveElement } from '../../../infrastructure/platform'; import Text from '../../common/Text'; export interface ReportDialogProps { visible: boolean; targetType: ReportTargetType; targetId: string; onClose: () => void; onSuccess?: () => void; } // 目标类型配置 const TARGET_CONFIG: Record = { post: { label: '帖子', icon: 'file-document-outline', color: '#FF6B35' }, comment: { label: '评论', icon: 'comment-text-outline', color: '#4CAF50' }, message: { label: '消息', icon: 'message-text-outline', color: '#2196F3' }, }; // 举报原因详细配置(带图标和说明) const REPORT_REASONS_DETAILED = [ { value: 'spam' as ReportReason, label: '垃圾广告', icon: 'email-newsletter', description: '包含广告、推广或重复内容' }, { value: 'inappropriate' as ReportReason, label: '违规内容', icon: 'alert-circle-outline', description: '包含违法、色情或暴力内容' }, { value: 'harassment' as ReportReason, label: '辱骂攻击', icon: 'account-off-outline', description: '包含人身攻击、骚扰或歧视' }, { value: 'misinformation' as ReportReason, label: '虚假信息', icon: 'alert-outline', description: '包含谣言、诈骗或误导信息' }, { value: 'other' as ReportReason, label: '其他原因', icon: 'dots-horizontal-circle-outline', description: '其他需要举报的情况' }, ]; const ReportDialog: React.FC = ({ visible, targetType, targetId, onClose, onSuccess, }) => { const colors = useAppColors(); const styles = useStyles(colors); const [selectedReason, setSelectedReason] = useState(null); const [description, setDescription] = useState(''); const [submitting, setSubmitting] = useState(false); const targetConfig = TARGET_CONFIG[targetType]; useEffect(() => { if (visible) { blurActiveElement(); } }, [visible]); // 判断补充说明是否必填(选择"其他原因"时必填) const isDescriptionRequired = selectedReason === 'other'; const descriptionPlaceholder = isDescriptionRequired ? '请详细描述您举报的原因(必填)...' : '请提供更多详细信息(选填)...'; // 提交举报 const handleSubmit = useCallback(async () => { if (!selectedReason) { Alert.alert('提示', '请选择举报原因', [{ text: '确定' }]); return; } if (isDescriptionRequired && !description.trim()) { Alert.alert('提示', '请选择"其他原因"时,请详细描述举报原因', [{ text: '确定' }]); return; } setSubmitting(true); try { const result = await reportService.createReport( targetType, targetId, selectedReason, description.trim() || undefined ); if (result) { Alert.alert('举报成功', '感谢您的反馈,我们会尽快处理', [ { text: '确定', onPress: () => { onClose(); onSuccess?.(); }, }, ]); } else { Alert.alert('举报失败', '请稍后重试', [{ text: '确定' }]); } } catch (error) { console.error('Submit report error:', error); Alert.alert('举报失败', '网络错误,请稍后重试', [{ text: '确定' }]); } finally { setSubmitting(false); } }, [selectedReason, description, targetType, targetId, onClose, onSuccess]); // 重置状态 const handleClose = useCallback(() => { setSelectedReason(null); setDescription(''); onClose(); }, [onClose]); const selectedReasonData = useMemo( () => REPORT_REASONS_DETAILED.find(r => r.value === selectedReason), [selectedReason] ); return ( {/* 头部 */} 举报{targetConfig.label} 请选择合适的举报原因 {/* 内容 */} {/* 举报原因选择 */} 举报原因 {REPORT_REASONS_DETAILED.map((item) => ( setSelectedReason(item.value)} disabled={submitting} activeOpacity={0.7} > {item.label} {item.description} {selectedReason === item.value && ( )} ))} {/* 补充说明 */} 补充说明{isDescriptionRequired ? ' *' : ''} {description.length}/500 {/* 底部按钮 */} 取消 {submitting ? ( ) : ( 提交举报 )} ); }; // 创建样式 const useStyles = (colors: ReturnType) => StyleSheet.create({ keyboardView: { flex: 1, }, overlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center', padding: spacing.lg, }, container: { backgroundColor: colors.chat.card, borderRadius: borderRadius.xl, width: '100%', maxWidth: 420, maxHeight: '85%', ...shadows.lg, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: spacing.lg, paddingBottom: spacing.md, }, headerContent: { flexDirection: 'row', alignItems: 'center', gap: spacing.md, }, headerIcon: { width: 40, height: 40, borderRadius: borderRadius.md, justifyContent: 'center', alignItems: 'center', }, headerText: { flex: 1, }, title: { fontSize: fontSizes.xl, fontWeight: '600', color: colors.chat.textPrimary, }, subtitle: { fontSize: fontSizes.sm, color: colors.chat.textSecondary, marginTop: 2, }, closeButton: { padding: spacing.xs, borderRadius: borderRadius.full, }, content: { padding: spacing.lg, paddingTop: 0, maxHeight: 400, }, section: { marginBottom: spacing.lg, }, sectionHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: spacing.sm, }, sectionTitle: { fontSize: fontSizes.md, fontWeight: '600', color: colors.chat.textPrimary, }, reasonList: { gap: spacing.sm, }, reasonItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: spacing.md, borderRadius: borderRadius.md, borderWidth: 1, borderColor: 'transparent', backgroundColor: colors.chat.surfaceMuted, }, reasonItemSelected: { backgroundColor: colors.primary.main + '08', borderWidth: 1, }, reasonLeft: { flexDirection: 'row', alignItems: 'center', flex: 1, gap: spacing.md, }, reasonIcon: { width: 36, height: 36, borderRadius: borderRadius.md, backgroundColor: colors.chat.surfaceRaised, justifyContent: 'center', alignItems: 'center', }, reasonIconSelected: { backgroundColor: colors.primary.main + '15', }, reasonTextContainer: { flex: 1, }, reasonLabel: { fontSize: fontSizes.md, fontWeight: '500', color: colors.chat.textPrimary, }, reasonLabelSelected: { color: colors.primary.main, fontWeight: '600', }, reasonDescription: { fontSize: fontSizes.sm, color: colors.chat.textSecondary, marginTop: 2, }, radio: { width: 22, height: 22, borderRadius: 11, borderWidth: 2, borderColor: colors.chat.border, justifyContent: 'center', alignItems: 'center', marginLeft: spacing.sm, }, radioSelected: { borderColor: colors.primary.main, backgroundColor: colors.primary.main, }, radioDot: { width: 10, height: 10, borderRadius: 5, backgroundColor: '#FFFFFF', }, textInput: { borderWidth: 1, borderColor: colors.chat.border, borderRadius: borderRadius.md, padding: spacing.md, fontSize: fontSizes.md, color: colors.chat.textPrimary, minHeight: 100, backgroundColor: colors.chat.surfaceMuted, }, textInputError: { borderColor: colors.error.main, backgroundColor: colors.error.main + '08', }, charCount: { fontSize: fontSizes.sm, color: colors.chat.textSecondary, }, footer: { flexDirection: 'row', padding: spacing.lg, gap: spacing.md, borderTopWidth: 1, borderTopColor: colors.chat.borderLight, }, cancelButton: { flex: 1, paddingVertical: spacing.md, borderRadius: borderRadius.md, borderWidth: 1, borderColor: colors.chat.border, alignItems: 'center', backgroundColor: colors.chat.surfaceMuted, }, cancelButtonText: { fontSize: fontSizes.md, fontWeight: '500', color: colors.chat.textSecondary, }, submitButton: { flex: 1, paddingVertical: spacing.md, borderRadius: borderRadius.md, backgroundColor: colors.primary.main, alignItems: 'center', }, submitButtonDisabled: { backgroundColor: colors.background.disabled, }, submitButtonText: { fontSize: fontSizes.md, color: '#FFFFFF', fontWeight: '600', }, }); export default ReportDialog;