feat(CommentItem, PostDetailScreen, ReportDialog): integrate report functionality
Some checks failed
Frontend CI / build-and-push-web (push) Successful in 2m54s
Frontend CI / build-android-apk (push) Has been cancelled
Frontend CI / ota-android (push) Has been cancelled

- Added report button to CommentItem for reporting comments and replies.
- Integrated ReportDialog into PostDetailScreen for handling report submissions.
- Enhanced ReportDialog with detailed report reasons and improved UI interactions.
- Updated styles across various components for a more modern look and feel.

Made-with: Cursor
This commit is contained in:
lafay
2026-03-30 17:53:30 +08:00
parent e0ee29caf8
commit 774b5c4b47
14 changed files with 793 additions and 373 deletions

View File

@@ -215,6 +215,7 @@ const CommentItem: React.FC<CommentItemProps> = ({
onDelete,
onImagePress,
currentUserId,
onReport,
}) => {
const colors = useAppColors();
const styles = useMemo(() => createCommentItemStyles(colors), [colors]);
@@ -541,6 +542,18 @@ const CommentItem: React.FC<CommentItemProps> = ({
</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>
@@ -635,6 +648,19 @@ const CommentItem: React.FC<CommentItemProps> = ({
</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

View File

@@ -1,24 +1,27 @@
/**
* 举报对话框组件
* 支持举报帖子、评论、消息
* 提供友好的用户界面和流畅的交互体验
*/
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import {
Modal,
View,
Text,
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 } from '../../../theme';
import { spacing, borderRadius, fontSizes, shadows } from '../../../theme';
import reportService, { ReportReason, ReportTargetType, REPORT_REASONS } from '../../../services/reportService';
import Text from '../../common/Text';
export interface ReportDialogProps {
visible: boolean;
@@ -28,14 +31,23 @@ export interface ReportDialogProps {
onSuccess?: () => void;
}
// 目标类型中文名称映射
const TARGET_TYPE_LABELS: Record<ReportTargetType, string> = {
post: '帖子',
comment: '评论',
message: '消息',
// 目标类型配置
const TARGET_CONFIG: Record<ReportTargetType, { label: string; icon: string; color: string }> = {
post: { label: '帖子', icon: 'file-document-outline', color: '#FF6B35' },
comment: { label: '评论', icon: 'comment-text-outline', color: '#4CAF50' },
message: { label: '消息', icon: 'message-text-outline', color: '#2196F3' },
};
export const ReportDialog: React.FC<ReportDialogProps> = ({
// 举报原因详细配置(带图标和说明)
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<ReportDialogProps> = ({
visible,
targetType,
targetId,
@@ -48,10 +60,23 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
const [description, setDescription] = useState('');
const [submitting, setSubmitting] = useState(false);
const targetConfig = TARGET_CONFIG[targetType];
// 判断补充说明是否必填(选择"其他原因"时必填)
const isDescriptionRequired = selectedReason === 'other';
const descriptionPlaceholder = isDescriptionRequired
? '请详细描述您举报的原因(必填)...'
: '请提供更多详细信息(选填)...';
// 提交举报
const handleSubmit = useCallback(async () => {
if (!selectedReason) {
Alert.alert('提示', '请选择举报原因');
Alert.alert('提示', '请选择举报原因', [{ text: '确定' }]);
return;
}
if (isDescriptionRequired && !description.trim()) {
Alert.alert('提示', '请选择"其他原因"时,请详细描述举报原因', [{ text: '确定' }]);
return;
}
@@ -75,11 +100,11 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
},
]);
} else {
Alert.alert('举报失败', '请稍后重试');
Alert.alert('举报失败', '请稍后重试', [{ text: '确定' }]);
}
} catch (error) {
console.error('Submit report error:', error);
Alert.alert('举报失败', '网络错误,请稍后重试');
Alert.alert('举报失败', '网络错误,请稍后重试', [{ text: '确定' }]);
} finally {
setSubmitting(false);
}
@@ -92,29 +117,49 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
onClose();
}, [onClose]);
const selectedReasonData = useMemo(
() => REPORT_REASONS_DETAILED.find(r => r.value === selectedReason),
[selectedReason]
);
return (
<Modal
visible={visible}
transparent
animationType="fade"
onRequestClose={handleClose}
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.keyboardView}
>
<View style={styles.overlay}>
<View style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>
{TARGET_TYPE_LABELS[targetType]}
</Text>
<View style={styles.headerContent}>
<View style={[styles.headerIcon, { backgroundColor: targetConfig.color + '15' }]}>
<MaterialCommunityIcons
name={targetConfig.icon as any}
size={20}
color={targetConfig.color}
/>
</View>
<View style={styles.headerText}>
<Text style={styles.title}>{targetConfig.label}</Text>
<Text style={styles.subtitle}></Text>
</View>
</View>
<TouchableOpacity
style={styles.closeButton}
onPress={handleClose}
disabled={submitting}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<MaterialCommunityIcons
name="close"
size={24}
color={colors.textSecondary}
color={colors.chat.textSecondary}
/>
</TouchableOpacity>
</View>
@@ -122,19 +167,46 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
{/* 内容 */}
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
{/* 举报原因选择 */}
<Text style={styles.sectionTitle}></Text>
<View style={styles.section}>
<Text style={styles.sectionTitle}></Text>
<View style={styles.reasonList}>
{REPORT_REASONS.map((item) => (
{REPORT_REASONS_DETAILED.map((item) => (
<TouchableOpacity
key={item.value}
style={[
styles.reasonItem,
selectedReason === item.value && styles.reasonItemSelected,
selectedReason === item.value && [
styles.reasonItemSelected,
{ borderColor: colors.primary.main + '40' }
],
]}
onPress={() => setSelectedReason(item.value)}
disabled={submitting}
activeOpacity={0.7}
>
<View style={styles.radioContainer}>
<View style={styles.reasonLeft}>
<View
style={[
styles.reasonIcon,
selectedReason === item.value && styles.reasonIconSelected,
]}
>
<MaterialCommunityIcons
name={item.icon as any}
size={20}
color={selectedReason === item.value ? colors.primary.main : colors.chat.textSecondary}
/>
</View>
<View style={styles.reasonTextContainer}>
<Text style={[
styles.reasonLabel,
...(selectedReason === item.value ? [styles.reasonLabelSelected] : []),
]}>
{item.label}
</Text>
<Text style={styles.reasonDescription}>{item.description}</Text>
</View>
</View>
<View
style={[
styles.radio,
@@ -145,25 +217,34 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
<View style={styles.radioDot} />
)}
</View>
<Text style={styles.reasonLabel}>{item.label}</Text>
</View>
</TouchableOpacity>
))}
</View>
</View>
{/* 补充说明 */}
<Text style={styles.sectionTitle}></Text>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>
{isDescriptionRequired ? ' *' : ''}
</Text>
<Text style={styles.charCount}>{description.length}/500</Text>
</View>
<TextInput
style={styles.textInput}
placeholder="请提供更多详细信息..."
placeholderTextColor={colors.textTertiary}
style={[
styles.textInput,
isDescriptionRequired && !description && styles.textInputError,
]}
placeholder={descriptionPlaceholder}
placeholderTextColor={colors.chat.textPlaceholder}
multiline
maxLength={500}
value={description}
onChangeText={setDescription}
editable={!submitting}
textAlignVertical="top"
/>
<Text style={styles.charCount}>{description.length}/500</Text>
</View>
</ScrollView>
{/* 底部按钮 */}
@@ -172,6 +253,7 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
style={styles.cancelButton}
onPress={handleClose}
disabled={submitting}
activeOpacity={0.7}
>
<Text style={styles.cancelButtonText}></Text>
</TouchableOpacity>
@@ -182,6 +264,7 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
]}
onPress={handleSubmit}
disabled={!selectedReason || submitting}
activeOpacity={0.9}
>
{submitting ? (
<ActivityIndicator size="small" color="#FFFFFF" />
@@ -192,6 +275,7 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
</View>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
);
};
@@ -199,6 +283,9 @@ export const ReportDialog: React.FC<ReportDialogProps> = ({
// 创建样式
const useStyles = (colors: ReturnType<typeof useAppColors>) =>
StyleSheet.create({
keyboardView: {
flex: 1,
},
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
@@ -207,128 +294,192 @@ const useStyles = (colors: ReturnType<typeof useAppColors>) =>
padding: spacing.lg,
},
container: {
backgroundColor: colors.backgroundPrimary,
borderRadius: borderRadius.lg,
backgroundColor: colors.chat.card,
borderRadius: borderRadius.xl,
width: '100%',
maxWidth: 400,
maxHeight: '80%',
maxWidth: 420,
maxHeight: '85%',
...shadows.lg,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: spacing.lg,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
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.lg,
fontSize: fontSizes.xl,
fontWeight: '600',
color: colors.textPrimary,
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: '500',
color: colors.textPrimary,
marginBottom: spacing.sm,
fontWeight: '600',
color: colors.chat.textPrimary,
},
reasonList: {
marginBottom: spacing.lg,
gap: spacing.sm,
},
reasonItem: {
paddingVertical: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: spacing.md,
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: 'transparent',
backgroundColor: colors.chat.surfaceMuted,
},
reasonItemSelected: {
backgroundColor: colors.backgroundSecondary,
marginHorizontal: -spacing.sm,
paddingHorizontal: spacing.sm,
borderRadius: borderRadius.sm,
backgroundColor: colors.primary.main + '08',
borderWidth: 1,
},
radioContainer: {
reasonLeft: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
gap: spacing.md,
},
radio: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: colors.border,
reasonIcon: {
width: 36,
height: 36,
borderRadius: borderRadius.md,
backgroundColor: colors.chat.surfaceRaised,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.sm,
},
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,
borderColor: colors.primary.main,
backgroundColor: colors.primary.main,
},
radioDot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: colors.primary,
},
reasonLabel: {
fontSize: fontSizes.md,
color: colors.textPrimary,
backgroundColor: '#FFFFFF',
},
textInput: {
borderWidth: 1,
borderColor: colors.border,
borderColor: colors.chat.border,
borderRadius: borderRadius.md,
padding: spacing.sm,
padding: spacing.md,
fontSize: fontSizes.md,
color: colors.textPrimary,
color: colors.chat.textPrimary,
minHeight: 100,
textAlignVertical: 'top',
backgroundColor: colors.chat.surfaceMuted,
},
textInputError: {
borderColor: colors.error.main,
backgroundColor: colors.error.main + '08',
},
charCount: {
fontSize: fontSizes.sm,
color: colors.textTertiary,
textAlign: 'right',
marginTop: spacing.xs,
color: colors.chat.textSecondary,
},
footer: {
flexDirection: 'row',
padding: spacing.lg,
gap: spacing.md,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
gap: spacing.sm,
borderTopColor: colors.chat.borderLight,
},
cancelButton: {
flex: 1,
paddingVertical: spacing.sm,
paddingVertical: spacing.md,
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: colors.border,
borderColor: colors.chat.border,
alignItems: 'center',
backgroundColor: colors.chat.surfaceMuted,
},
cancelButtonText: {
fontSize: fontSizes.md,
color: colors.textSecondary,
fontWeight: '500',
color: colors.chat.textSecondary,
},
submitButton: {
flex: 1,
paddingVertical: spacing.sm,
paddingVertical: spacing.md,
borderRadius: borderRadius.md,
backgroundColor: colors.primary,
backgroundColor: colors.primary.main,
alignItems: 'center',
},
submitButtonDisabled: {
backgroundColor: colors.primary + '60',
backgroundColor: colors.background.disabled,
},
submitButtonText: {
fontSize: fontSizes.md,
color: '#FFFFFF',
fontWeight: '500',
fontWeight: '600',
},
});

View File

@@ -1,2 +1,2 @@
export { ReportDialog } from './ReportDialog';
export { default } from './ReportDialog';
export type { ReportDialogProps } from './ReportDialog';

View File

@@ -39,7 +39,7 @@ import { useCurrentUser } from '../../stores/authStore';
import { postService, commentService, uploadService, authService, showPrompt, voteService } from '../../services';
import { processPostUseCase } from '../../core/usecases/ProcessPostUseCase';
import { useCursorPagination } from '../../hooks/useCursorPagination';
import { CommentItem, VoteCard } from '../../components/business';
import { CommentItem, VoteCard, ReportDialog } from '../../components/business';
import { Avatar, Button, Loading, EmptyState, Text, ImageGallery, ImageGrid, ImageGridItem, AdaptiveLayout, AppBackButton } from '../../components/common';
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks/useResponsive';
import * as hrefs from '../../navigation/hrefs';
@@ -177,6 +177,10 @@ export const PostDetailScreen: React.FC = () => {
const flatListRef = useRef<FlatList>(null);
const hasRecordedView = useRef(false); // 是否已记录浏览量
// 举报相关状态
const [reportDialogVisible, setReportDialogVisible] = useState(false);
const [reportTarget, setReportTarget] = useState<{ type: 'post' | 'comment'; id: string } | null>(null);
// 投票相关状态
const [voteResult, setVoteResult] = useState<VoteResultDTO | null>(null);
const [isVoteLoading, setIsVoteLoading] = useState(false);
@@ -1243,6 +1247,25 @@ export const PostDetailScreen: React.FC = () => {
</TouchableOpacity>
</View>
)}
{/* 举报按钮 - 只对非帖子作者显示 */}
{currentUser?.id !== post.author?.id && (
<TouchableOpacity
style={styles.reportButtonInline}
onPress={() => {
setReportTarget({ type: 'post', id: post.id });
setReportDialogVisible(true);
}}
>
<MaterialCommunityIcons
name="flag-outline"
size={14}
color={colors.text.hint}
/>
<Text variant="caption" color={colors.text.hint} style={styles.reportButtonText}>
</Text>
</TouchableOpacity>
)}
</View>
{/* 底部操作栏 - QQ频道风格 */}
@@ -1347,6 +1370,12 @@ export const PostDetailScreen: React.FC = () => {
}
};
// 处理举报评论
const handleReportComment = useCallback((comment: Comment) => {
setReportTarget({ type: 'comment', id: comment.id });
setReportDialogVisible(true);
}, []);
// 渲染评论 - 带楼层号
const commentKeyExtractor = useCallback((item: Comment) => item.id, []);
@@ -1372,9 +1401,10 @@ export const PostDetailScreen: React.FC = () => {
onDelete={handleDeleteComment}
onImagePress={handleImagePress}
currentUserId={currentUser?.id}
onReport={handleReportComment}
/>
);
}, [currentUser?.id, handleDeleteComment, handleImagePress, handleLikeComment, handleLoadMoreReplies, post?.user_id]);
}, [currentUser?.id, handleDeleteComment, handleImagePress, handleLikeComment, handleLoadMoreReplies, post?.user_id, handleReportComment]);
// 渲染空评论 - 现代化设计
const renderEmptyComments = useCallback(() => (
@@ -1696,6 +1726,20 @@ export const PostDetailScreen: React.FC = () => {
onClose={() => setShowImageModal(false)}
enableSave
/>
{/* 举报对话框 */}
<ReportDialog
visible={reportDialogVisible}
targetType={reportTarget?.type || 'post'}
targetId={reportTarget?.id || ''}
onClose={() => {
setReportDialogVisible(false);
setReportTarget(null);
}}
onSuccess={() => {
setReportTarget(null);
}}
/>
</SafeAreaView>
);
};
@@ -1858,7 +1902,13 @@ function createPostDetailStyles(colors: AppColors) {
alignItems: 'center',
padding: spacing.xs,
},
editButtonText: {
reportButtonInline: {
flexDirection: 'row',
alignItems: 'center',
marginLeft: spacing.sm,
padding: spacing.xs,
},
reportButtonText: {
marginLeft: 2,
fontSize: fontSizes.sm,
},

View File

@@ -322,10 +322,10 @@ function createCreateGroupStyles(colors: AppColors) {
flex: 1,
},
scrollContent: {
padding: spacing.lg,
paddingBottom: spacing.xl,
padding: spacing.md,
paddingBottom: spacing.xl * 2,
},
// 头部区域样式
// 头部区域样式 - 扁平化
headerSection: {
flexDirection: 'row',
alignItems: 'flex-start',
@@ -341,12 +341,17 @@ function createCreateGroupStyles(colors: AppColors) {
width: 80,
height: 80,
borderRadius: 40,
borderWidth: 3,
borderColor: colors.primary.light + '40',
},
avatarPlaceholder: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background.default,
borderRadius: 40,
borderWidth: 2,
borderColor: colors.divider,
borderStyle: 'dashed',
},
avatarBadge: {
position: 'absolute',
@@ -358,7 +363,7 @@ function createCreateGroupStyles(colors: AppColors) {
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderWidth: 3,
borderColor: colors.background.paper,
},
nameInputContainer: {
@@ -367,7 +372,9 @@ function createCreateGroupStyles(colors: AppColors) {
},
inputLabel: {
marginBottom: spacing.sm,
fontWeight: '600',
fontWeight: '700',
fontSize: fontSizes.sm + 1,
letterSpacing: 0.3,
},
nameInput: {
backgroundColor: colors.background.paper,
@@ -376,12 +383,15 @@ function createCreateGroupStyles(colors: AppColors) {
paddingVertical: spacing.md,
fontSize: fontSizes.lg,
color: colors.text.primary,
borderWidth: 1,
borderColor: colors.divider,
borderWidth: 1.5,
borderColor: colors.divider + '80',
fontWeight: '600',
},
charCount: {
textAlign: 'right',
marginTop: spacing.xs,
fontWeight: '500',
fontSize: fontSizes.sm,
},
// 区域样式
section: {
@@ -391,18 +401,20 @@ function createCreateGroupStyles(colors: AppColors) {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.sm,
marginBottom: spacing.md,
},
sectionTitle: {
fontWeight: '600',
fontWeight: '800',
fontSize: fontSizes.md + 1,
marginBottom: spacing.sm,
letterSpacing: 0.3,
},
// 文本域样式
// 文本域样式 - 更现代
textAreaContainer: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
borderWidth: 1,
borderColor: colors.divider,
borderWidth: 1.5,
borderColor: colors.divider + '80',
padding: spacing.md,
},
textArea: {
@@ -414,6 +426,8 @@ function createCreateGroupStyles(colors: AppColors) {
textAreaCharCount: {
textAlign: 'right',
marginTop: spacing.sm,
fontWeight: '500',
fontSize: fontSizes.sm,
},
// 已选成员样式
selectedMembersList: {
@@ -443,8 +457,10 @@ function createCreateGroupStyles(colors: AppColors) {
marginTop: spacing.xs,
textAlign: 'center',
width: '100%',
fontWeight: '600',
fontSize: fontSizes.sm,
},
// 邀请按钮样式
// 邀请按钮样式 - 扁平化
inviteButton: {
flexDirection: 'row',
alignItems: 'center',
@@ -452,13 +468,14 @@ function createCreateGroupStyles(colors: AppColors) {
borderRadius: borderRadius.lg,
padding: spacing.md,
marginBottom: spacing.xl,
...shadows.sm,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
inviteIconContainer: {
width: 48,
height: 48,
borderRadius: borderRadius.lg,
backgroundColor: colors.primary.light + '20',
backgroundColor: colors.primary.light + '15',
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
@@ -467,16 +484,18 @@ function createCreateGroupStyles(colors: AppColors) {
flex: 1,
},
inviteTitle: {
fontWeight: '600',
fontWeight: '700',
fontSize: fontSizes.md + 1,
marginBottom: 2,
letterSpacing: 0.3,
},
// 底部按钮样式
footer: {
padding: spacing.lg,
paddingBottom: spacing.xl,
backgroundColor: colors.background.paper,
borderTopWidth: 1,
borderTopColor: colors.divider,
borderTopWidth: 0.5,
borderTopColor: colors.divider + '60',
},
});
}

View File

@@ -1091,13 +1091,15 @@ function createGroupInfoStyles(colors: AppColors) {
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
paddingVertical: spacing.md,
backgroundColor: colors.background.paper,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '60',
},
pageTitle: {
fontWeight: '600',
fontWeight: '700',
fontSize: fontSizes.xl,
letterSpacing: 0.5,
},
pageHeaderPlaceholder: {
width: 40,
@@ -1107,8 +1109,8 @@ function createGroupInfoStyles(colors: AppColors) {
flex: 1,
},
scrollContent: {
padding: spacing.lg,
paddingBottom: spacing.xl,
padding: spacing.md,
paddingBottom: spacing.xl * 2,
},
errorContainer: {
flex: 1,
@@ -1116,13 +1118,14 @@ function createGroupInfoStyles(colors: AppColors) {
alignItems: 'center',
},
// 头部卡片样式
// 头部卡片样式 - 扁平化设计
headerCard: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.xl,
padding: spacing.lg,
marginBottom: spacing.lg,
...shadows.sm,
marginBottom: spacing.md,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
groupHeader: {
flexDirection: 'row',
@@ -1134,30 +1137,33 @@ function createGroupInfoStyles(colors: AppColors) {
flex: 1,
},
groupName: {
fontWeight: '700',
fontWeight: '800',
fontSize: fontSizes.xl + 2,
marginBottom: spacing.xs,
letterSpacing: 0.3,
},
groupMeta: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
marginBottom: spacing.xs,
},
groupNoRow: {
marginTop: spacing.xs,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: spacing.sm,
},
copyGroupNoBtn: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: spacing.sm,
paddingVertical: 4,
borderRadius: borderRadius.sm,
backgroundColor: colors.primary.light + '20',
borderRadius: borderRadius.md,
backgroundColor: colors.primary.light + '15',
},
copyGroupNoBtnText: {
marginLeft: 4,
fontWeight: '500',
},
descriptionContainer: {
flexDirection: 'row',
@@ -1165,20 +1171,24 @@ function createGroupInfoStyles(colors: AppColors) {
backgroundColor: colors.background.default,
borderRadius: borderRadius.lg,
padding: spacing.md,
borderWidth: 0.5,
borderColor: colors.divider + '30',
},
groupDesc: {
marginLeft: spacing.sm,
flex: 1,
lineHeight: 20,
lineHeight: 22,
fontWeight: '400',
},
// 通用卡片样式
// 通用卡片样式 - 扁平化
card: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.xl,
padding: spacing.lg,
marginBottom: spacing.lg,
...shadows.sm,
marginBottom: spacing.md,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
cardHeader: {
flexDirection: 'row',
@@ -1189,33 +1199,41 @@ function createGroupInfoStyles(colors: AppColors) {
width: 36,
height: 36,
borderRadius: borderRadius.md,
backgroundColor: colors.info.light + '20',
backgroundColor: colors.info.light + '15',
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.sm,
},
cardTitle: {
fontWeight: '600',
fontWeight: '700',
fontSize: fontSizes.md + 1,
flex: 1,
letterSpacing: 0.3,
},
// 公告样式
// 公告样式 - 更现代的卡片
announcementContent: {
backgroundColor: colors.background.default,
backgroundColor: colors.warning.light + '08',
borderRadius: borderRadius.lg,
padding: spacing.md,
borderLeftWidth: 3,
borderLeftColor: colors.warning.main,
},
announcementText: {
lineHeight: 22,
marginBottom: spacing.sm,
fontWeight: '400',
},
announcementTime: {
textAlign: 'right',
fontWeight: '500',
},
// 成员预览样式
memberCount: {
marginRight: spacing.xs,
fontWeight: '600',
color: colors.text.secondary,
},
memberPreview: {
flexDirection: 'row',
@@ -1226,7 +1244,7 @@ function createGroupInfoStyles(colors: AppColors) {
},
memberAvatar: {
position: 'relative',
marginLeft: -10,
marginLeft: -8,
},
memberAvatarFirst: {
marginLeft: 0,
@@ -1239,12 +1257,12 @@ function createGroupInfoStyles(colors: AppColors) {
borderRadius: borderRadius.sm,
paddingHorizontal: 4,
paddingVertical: 1,
borderWidth: 1.5,
borderWidth: 2,
borderColor: colors.background.paper,
},
ownerBadgeText: {
fontSize: 8,
fontWeight: '700',
fontWeight: '800',
},
moreMembers: {
minWidth: 44,
@@ -1254,49 +1272,55 @@ function createGroupInfoStyles(colors: AppColors) {
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderColor: colors.divider,
marginLeft: -10,
borderColor: colors.divider + '60',
marginLeft: -8,
paddingHorizontal: spacing.xs,
},
moreMembersText: {
fontWeight: '600',
fontWeight: '700',
includeFontPadding: false,
fontSize: fontSizes.sm,
},
// 邀请按钮样式
// 邀请按钮样式 - 更加醒目
inviteButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: spacing.md,
borderRadius: borderRadius.lg,
backgroundColor: colors.primary.light + '15',
borderWidth: 1,
borderColor: colors.primary.light,
backgroundColor: colors.primary.light + '10',
borderWidth: 1.5,
borderColor: colors.primary.light + '60',
borderStyle: 'dashed',
},
inviteIconContainer: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: colors.background.paper,
backgroundColor: colors.primary.light + '20',
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.sm,
},
inviteButtonText: {
fontWeight: '600',
fontWeight: '700',
marginRight: spacing.xs,
letterSpacing: 0.3,
},
// 设置项样式
// 设置项样式 - 更加扁平
settingsList: {
marginTop: spacing.sm,
},
settingItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.sm,
paddingVertical: spacing.md,
borderRadius: borderRadius.md,
paddingHorizontal: spacing.sm,
marginLeft: -spacing.sm,
marginRight: -spacing.sm,
},
settingIconContainer: {
width: 40,
@@ -1308,24 +1332,29 @@ function createGroupInfoStyles(colors: AppColors) {
marginRight: spacing.md,
},
settingIconDanger: {
backgroundColor: colors.error.light + '20',
backgroundColor: colors.error.light + '15',
},
settingContent: {
flex: 1,
},
dangerText: {
color: colors.error.main,
fontWeight: '600',
},
settingDivider: {
marginLeft: 56,
width: 'auto',
marginVertical: spacing.xs,
opacity: 0.5,
},
// 危险操作卡片
// 危险操作卡片 - 更加醒目
dangerCard: {
padding: 0,
overflow: 'hidden',
borderTopWidth: 1,
borderTopColor: colors.error.light + '30',
marginTop: spacing.sm,
},
dangerButton: {
flexDirection: 'row',
@@ -1333,15 +1362,17 @@ function createGroupInfoStyles(colors: AppColors) {
justifyContent: 'center',
paddingVertical: spacing.lg,
gap: spacing.sm,
backgroundColor: colors.error.light + '08',
},
dangerButtonText: {
fontWeight: '600',
fontWeight: '700',
letterSpacing: 0.3,
},
// 模态框样式
// 模态框样式 - 更现代
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
backgroundColor: 'rgba(0, 0, 0, 0.45)',
justifyContent: 'flex-end',
},
modalContent: {
@@ -1350,7 +1381,7 @@ function createGroupInfoStyles(colors: AppColors) {
borderTopRightRadius: borderRadius['2xl'],
height: '80%',
paddingHorizontal: spacing.lg,
paddingTop: spacing.lg,
paddingTop: spacing.md,
paddingBottom: spacing.xl,
},
modalHeader: {
@@ -1358,24 +1389,29 @@ function createGroupInfoStyles(colors: AppColors) {
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: spacing.lg,
paddingBottom: spacing.md,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '60',
},
modalHeaderButton: {
paddingVertical: spacing.sm,
minWidth: 60,
},
modalTitle: {
fontWeight: '700',
fontSize: fontSizes.xl,
fontWeight: '800',
fontSize: fontSizes.xl + 1,
letterSpacing: 0.5,
},
saveButton: {
fontWeight: '600',
fontWeight: '700',
letterSpacing: 0.3,
},
confirmButton: {
fontWeight: '600',
fontWeight: '700',
textAlign: 'right',
},
// 编辑表单样式
// 编辑表单样式 - 更现代
editForm: {
alignItems: 'center',
},
@@ -1387,12 +1423,17 @@ function createGroupInfoStyles(colors: AppColors) {
width: 90,
height: 90,
borderRadius: 45,
borderWidth: 3,
borderColor: colors.primary.light + '40',
},
editAvatarPlaceholder: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background.default,
borderRadius: 45,
borderWidth: 2,
borderColor: colors.divider,
borderStyle: 'dashed',
},
editAvatarButton: {
position: 'absolute',
@@ -1413,7 +1454,9 @@ function createGroupInfoStyles(colors: AppColors) {
},
editLabel: {
marginBottom: spacing.sm,
fontWeight: '600',
fontWeight: '700',
fontSize: fontSizes.sm + 1,
letterSpacing: 0.3,
},
editInput: {
backgroundColor: colors.background.default,
@@ -1422,8 +1465,9 @@ function createGroupInfoStyles(colors: AppColors) {
paddingVertical: spacing.md,
fontSize: fontSizes.md,
color: colors.text.primary,
borderWidth: 1,
borderColor: colors.divider,
borderWidth: 1.5,
borderColor: colors.divider + '80',
fontWeight: '400',
},
editTextArea: {
minHeight: 100,
@@ -1433,6 +1477,7 @@ function createGroupInfoStyles(colors: AppColors) {
editCharCount: {
textAlign: 'right',
marginTop: spacing.xs,
fontWeight: '500',
},
// 公告表单样式
@@ -1446,17 +1491,18 @@ function createGroupInfoStyles(colors: AppColors) {
padding: spacing.md,
fontSize: fontSizes.md,
color: colors.text.primary,
borderWidth: 1,
borderColor: colors.divider,
borderWidth: 1.5,
borderColor: colors.divider + '80',
lineHeight: 22,
textAlignVertical: 'top',
},
announcementCharCount: {
textAlign: 'right',
marginTop: spacing.sm,
fontWeight: '500',
},
// 加群方式样式
// 加群方式样式 - 更现代的选中状态
joinTypeList: {
gap: spacing.md,
},
@@ -1465,36 +1511,39 @@ function createGroupInfoStyles(colors: AppColors) {
alignItems: 'center',
padding: spacing.md,
borderRadius: borderRadius.lg,
borderWidth: 1,
borderColor: colors.divider,
borderWidth: 1.5,
borderColor: colors.divider + '80',
backgroundColor: colors.background.default,
},
joinTypeItemSelected: {
borderColor: colors.primary.main,
backgroundColor: colors.primary.light + '10',
backgroundColor: colors.primary.light + '12',
borderWidth: 2,
},
joinTypeIcon: {
width: 48,
height: 48,
borderRadius: borderRadius.lg,
backgroundColor: colors.background.default,
backgroundColor: colors.background.paper,
justifyContent: 'center',
alignItems: 'center',
marginRight: spacing.md,
},
joinTypeIconSelected: {
backgroundColor: colors.primary.light + '20',
backgroundColor: colors.primary.light + '25',
},
joinTypeInfo: {
flex: 1,
},
joinTypeTextSelected: {
fontWeight: '600',
fontWeight: '700',
color: colors.primary.main,
},
// 转让群主样式
transferDesc: {
marginBottom: spacing.md,
fontWeight: '500',
},
transferList: {
flex: 1,
@@ -1504,11 +1553,15 @@ function createGroupInfoStyles(colors: AppColors) {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.md,
paddingHorizontal: spacing.md,
borderRadius: borderRadius.lg,
marginBottom: spacing.xs,
marginBottom: spacing.sm,
backgroundColor: colors.background.default,
},
transferItemSelected: {
backgroundColor: colors.primary.light + '15',
backgroundColor: colors.primary.light + '12',
borderWidth: 1.5,
borderColor: colors.primary.light + '60',
},
transferItemInfo: {
flex: 1,
@@ -1530,6 +1583,7 @@ function createGroupInfoStyles(colors: AppColors) {
borderColor: colors.divider,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background.paper,
},
transferRadioSelected: {
backgroundColor: colors.primary.main,
@@ -1546,6 +1600,8 @@ function createGroupInfoStyles(colors: AppColors) {
borderRadius: borderRadius.lg,
padding: spacing.xs,
marginBottom: spacing.md,
borderWidth: 1,
borderColor: colors.divider + '60',
},
tab: {
flex: 1,
@@ -1555,10 +1611,11 @@ function createGroupInfoStyles(colors: AppColors) {
},
tabActive: {
backgroundColor: colors.background.paper,
...shadows.sm,
borderWidth: 1,
borderColor: colors.divider + '60',
},
tabTextActive: {
fontWeight: '600',
fontWeight: '700',
},
selectedBadge: {
backgroundColor: colors.primary.light + '20',
@@ -1577,10 +1634,13 @@ function createGroupInfoStyles(colors: AppColors) {
paddingVertical: spacing.md,
paddingHorizontal: spacing.sm,
borderRadius: borderRadius.lg,
marginBottom: spacing.xs,
marginBottom: spacing.sm,
backgroundColor: colors.background.default,
},
friendItemSelected: {
backgroundColor: colors.primary.light + '15',
backgroundColor: colors.primary.light + '12',
borderWidth: 1.5,
borderColor: colors.primary.light + '60',
},
friendInfo: {
flex: 1,
@@ -1588,7 +1648,7 @@ function createGroupInfoStyles(colors: AppColors) {
marginRight: spacing.sm,
},
nickname: {
fontWeight: '500',
fontWeight: '600',
marginBottom: 2,
},
checkbox: {
@@ -1599,6 +1659,7 @@ function createGroupInfoStyles(colors: AppColors) {
borderColor: colors.divider,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background.paper,
},
checkboxSelected: {
backgroundColor: colors.primary.main,

View File

@@ -3,7 +3,7 @@ import { View, StyleSheet, ActivityIndicator, Alert, ScrollView, TouchableOpacit
import { useRouter, useLocalSearchParams } from 'expo-router';
import { SafeAreaView } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, borderRadius, shadows, useAppColors, type AppColors } from '../../theme';
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
import { Avatar, Text } from '../../components/common';
import { routePayloadCache } from '../../stores/routePayloadCache';
import { groupService } from '../../services/groupService';
@@ -196,14 +196,15 @@ function createGroupInviteDetailStyles(colors: AppColors) {
flex: 1,
},
scrollContent: {
padding: spacing.lg,
paddingBottom: spacing.xl,
padding: spacing.md,
paddingBottom: spacing.xl * 2,
},
card: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
borderRadius: borderRadius.xl,
padding: spacing.lg,
...shadows.sm,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
cardHeader: {
flexDirection: 'row',
@@ -211,20 +212,23 @@ function createGroupInviteDetailStyles(colors: AppColors) {
marginBottom: spacing.md,
},
cardIconContainer: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: colors.info.light + '30',
width: 36,
height: 36,
borderRadius: borderRadius.md,
backgroundColor: colors.info.light + '15',
alignItems: 'center',
justifyContent: 'center',
marginRight: spacing.sm,
},
cardTitle: {
fontWeight: '600',
fontWeight: '700',
fontSize: fontSizes.md + 1,
flex: 1,
letterSpacing: 0.3,
},
memberCount: {
marginRight: spacing.xs,
fontWeight: '600',
},
loadingWrap: {
paddingVertical: spacing.md,
@@ -247,13 +251,15 @@ function createGroupInviteDetailStyles(colors: AppColors) {
bottom: -2,
right: -2,
backgroundColor: colors.warning.main,
borderRadius: 8,
borderRadius: borderRadius.sm,
paddingHorizontal: 4,
paddingVertical: 1,
borderWidth: 1.5,
borderColor: colors.background.paper,
},
ownerBadgeText: {
fontSize: 10,
fontWeight: '700',
fontSize: 8,
fontWeight: '800',
},
});
}

View File

@@ -664,19 +664,21 @@ function createGroupMembersStyles(colors: AppColors) {
flex: 1,
backgroundColor: colors.background.default,
},
// Header 样式
// Header 样式 - 扁平化
pageHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
paddingVertical: spacing.md,
backgroundColor: colors.background.paper,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '60',
},
pageTitle: {
fontWeight: '600',
fontWeight: '800',
fontSize: fontSizes.xl + 1,
letterSpacing: 0.5,
},
pageHeaderPlaceholder: {
width: 40,
@@ -688,18 +690,20 @@ function createGroupMembersStyles(colors: AppColors) {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.md,
backgroundColor: colors.background.default,
borderTopWidth: 0.5,
borderTopColor: colors.divider + '40',
},
memberItem: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: spacing.md,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.md,
backgroundColor: colors.background.paper,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '40',
},
memberInfo: {
flex: 1,
@@ -711,63 +715,81 @@ function createGroupMembersStyles(colors: AppColors) {
marginBottom: 2,
},
memberName: {
fontWeight: '500',
fontWeight: '700',
fontSize: fontSizes.md + 1,
letterSpacing: 0.3,
},
roleBadge: {
paddingHorizontal: spacing.xs,
paddingHorizontal: spacing.sm,
paddingVertical: 2,
borderRadius: borderRadius.sm,
marginLeft: spacing.sm,
fontWeight: '800',
fontSize: fontSizes.xs,
color: colors.background.paper,
},
mutedBadge: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 2,
},
// 模态框样式
// 模态框样式 - 更现代
modalOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
backgroundColor: 'rgba(0, 0, 0, 0.45)',
justifyContent: 'flex-end',
},
modalContent: {
backgroundColor: colors.background.paper,
borderTopLeftRadius: borderRadius.lg,
borderTopRightRadius: borderRadius.lg,
borderTopLeftRadius: borderRadius['2xl'],
borderTopRightRadius: borderRadius['2xl'],
padding: spacing.lg,
maxHeight: '80%',
},
modalHeader: {
alignItems: 'center',
marginBottom: spacing.md,
marginBottom: spacing.lg,
paddingBottom: spacing.md,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '60',
},
modalTitle: {
fontWeight: '700',
fontWeight: '800',
fontSize: fontSizes.xl + 1,
marginTop: spacing.sm,
marginBottom: spacing.xs,
letterSpacing: 0.5,
},
actionItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.md,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
paddingHorizontal: spacing.sm,
marginLeft: -spacing.sm,
marginRight: -spacing.sm,
borderRadius: borderRadius.md,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '40',
},
actionText: {
marginLeft: spacing.md,
fontWeight: '600',
fontSize: fontSizes.md,
},
inputLabel: {
marginBottom: spacing.xs,
fontWeight: '700',
fontSize: fontSizes.sm + 1,
},
input: {
backgroundColor: colors.background.default,
borderRadius: borderRadius.md,
borderRadius: borderRadius.lg,
paddingHorizontal: spacing.md,
paddingVertical: spacing.md,
fontSize: fontSizes.md,
color: colors.text.primary,
borderWidth: 1,
borderColor: colors.divider,
borderWidth: 1.5,
borderColor: colors.divider + '80',
marginBottom: spacing.md,
},
modalButtons: {
@@ -791,6 +813,8 @@ function createGroupMembersStyles(colors: AppColors) {
noMoreText: {
textAlign: 'center',
paddingVertical: spacing.md,
fontWeight: '500',
color: colors.text.hint,
},
});
}

View File

@@ -13,7 +13,7 @@ import {
import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, borderRadius, useAppColors, type AppColors } from '../../theme';
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
import Avatar from '../../components/common/Avatar';
import Text from '../../components/common/Text';
import { groupService } from '../../services/groupService';
@@ -27,47 +27,58 @@ function createJoinGroupStyles(colors: AppColors) {
container: {
flex: 1,
backgroundColor: colors.background.default,
padding: spacing.lg,
padding: spacing.md,
},
heroCard: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
borderRadius: borderRadius.xl,
padding: spacing.lg,
marginBottom: spacing.lg,
marginBottom: spacing.md,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
heroIconWrap: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: colors.primary.light + '26',
backgroundColor: colors.primary.light + '15',
alignItems: 'center',
justifyContent: 'center',
marginBottom: spacing.md,
},
heroTitle: {
marginBottom: spacing.xs,
fontWeight: '800',
fontSize: fontSizes.xl + 1,
letterSpacing: 0.3,
},
tip: {
lineHeight: 20,
lineHeight: 22,
fontWeight: '400',
},
formCard: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
borderRadius: borderRadius.xl,
padding: spacing.lg,
flex: 1,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
label: {
marginBottom: spacing.xs,
fontWeight: '700',
fontSize: fontSizes.sm + 1,
},
input: {
flex: 1,
borderWidth: 1,
borderColor: colors.divider,
borderRadius: borderRadius.md,
backgroundColor: colors.background.paper,
borderWidth: 1.5,
borderColor: colors.divider + '80',
borderRadius: borderRadius.lg,
backgroundColor: colors.background.default,
paddingHorizontal: spacing.md,
paddingVertical: spacing.md,
color: colors.text.primary,
fontSize: fontSizes.md,
},
searchRow: {
flexDirection: 'row',
@@ -77,26 +88,28 @@ function createJoinGroupStyles(colors: AppColors) {
searchBtn: {
width: 46,
height: 46,
borderRadius: borderRadius.md,
borderRadius: borderRadius.lg,
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
marginLeft: spacing.sm,
},
searchResultSection: {
marginBottom: spacing.lg,
marginBottom: spacing.md,
},
sectionTitle: {
marginBottom: spacing.sm,
fontWeight: '600',
fontWeight: '800',
fontSize: fontSizes.md + 1,
letterSpacing: 0.3,
},
listSection: {
flex: 1,
},
groupCard: {
borderWidth: 1,
borderColor: colors.divider,
borderRadius: borderRadius.md,
borderWidth: 0.5,
borderColor: colors.divider + '40',
borderRadius: borderRadius.lg,
padding: spacing.md,
backgroundColor: colors.background.default,
marginBottom: spacing.md,
@@ -111,11 +124,14 @@ function createJoinGroupStyles(colors: AppColors) {
},
groupName: {
marginBottom: spacing.xs,
fontWeight: '600',
fontWeight: '700',
fontSize: fontSizes.md + 1,
letterSpacing: 0.3,
},
groupDesc: {
marginTop: spacing.sm,
lineHeight: 18,
lineHeight: 20,
fontWeight: '400',
},
groupInfoRow: {
marginTop: spacing.sm,
@@ -135,16 +151,17 @@ function createJoinGroupStyles(colors: AppColors) {
paddingHorizontal: spacing.sm,
paddingVertical: 4,
borderRadius: borderRadius.sm,
backgroundColor: colors.primary.light + '22',
backgroundColor: colors.primary.light + '15',
},
copyBtnText: {
marginLeft: 4,
fontWeight: '600',
},
submitBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: borderRadius.md,
borderRadius: borderRadius.lg,
backgroundColor: colors.primary.main,
minHeight: 46,
},
@@ -153,10 +170,13 @@ function createJoinGroupStyles(colors: AppColors) {
},
submitText: {
marginLeft: spacing.xs,
fontWeight: '700',
fontSize: fontSizes.md,
},
emptyText: {
marginTop: spacing.sm,
textAlign: 'center',
fontWeight: '500',
},
loadingFooter: {
paddingVertical: spacing.md,
@@ -169,6 +189,8 @@ function createJoinGroupStyles(colors: AppColors) {
noMoreText: {
textAlign: 'center',
paddingVertical: spacing.md,
fontWeight: '500',
color: colors.text.hint,
},
});
}

View File

@@ -185,9 +185,11 @@ export const ChatInput: React.FC<ChatInputProps & {
<TouchableOpacity
style={styles.sendButtonActive}
onPress={onSend}
activeOpacity={0.8}
activeOpacity={0.85}
>
<MaterialCommunityIcons name="send" size={20} color="#FFF" />
{/* 光泽层 - 模拟渐变效果 */}
<View style={styles.sendButtonShine} pointerEvents="none" />
<MaterialCommunityIcons name="send" size={18} color="#FFF" />
</TouchableOpacity>
) : isComposerBusy && !isDisabled ? (
<TouchableOpacity

View File

@@ -374,7 +374,7 @@ function createGroupInfoPanelStyles(colors: AppColors) {
return StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.3)',
backgroundColor: 'rgba(0, 0, 0, 0.35)',
zIndex: 100,
},
overlayTouchable: {
@@ -388,24 +388,28 @@ function createGroupInfoPanelStyles(colors: AppColors) {
width: PANEL_WIDTH,
backgroundColor: colors.background.paper,
zIndex: 101,
...shadows.lg,
borderLeftWidth: 0.5,
borderLeftColor: colors.divider + '60',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.md,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.md,
borderBottomWidth: 1,
borderBottomColor: colors.divider,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '60',
backgroundColor: colors.background.paper,
},
headerTitle: {
fontSize: fontSizes.xl,
fontWeight: '600',
fontSize: fontSizes.xl + 1,
fontWeight: '800',
color: colors.text.primary,
letterSpacing: 0.5,
},
closeButton: {
padding: spacing.xs,
borderRadius: borderRadius.md,
},
content: {
flex: 1,
@@ -413,22 +417,26 @@ function createGroupInfoPanelStyles(colors: AppColors) {
groupInfoSection: {
alignItems: 'center',
paddingVertical: spacing.xl,
paddingHorizontal: spacing.md,
},
groupName: {
fontSize: fontSizes.xl,
fontWeight: '600',
fontSize: fontSizes.xl + 1,
fontWeight: '800',
color: colors.text.primary,
marginTop: spacing.md,
letterSpacing: 0.3,
},
memberCount: {
fontSize: fontSizes.md,
color: colors.text.secondary,
marginTop: spacing.xs,
fontWeight: '500',
},
joinType: {
fontSize: fontSizes.sm,
color: colors.primary.main,
marginTop: spacing.xs,
fontWeight: '600',
},
inviteButton: {
flexDirection: 'row',
@@ -436,25 +444,27 @@ function createGroupInfoPanelStyles(colors: AppColors) {
justifyContent: 'center',
paddingVertical: spacing.sm,
paddingHorizontal: spacing.md,
backgroundColor: colors.primary.light + '15',
borderRadius: 20,
backgroundColor: colors.primary.light + '12',
borderRadius: borderRadius.lg,
marginTop: spacing.md,
borderWidth: 1,
borderColor: colors.primary.light,
borderWidth: 1.5,
borderColor: colors.primary.light + '60',
borderStyle: 'dashed',
},
inviteButtonText: {
fontSize: fontSizes.sm,
color: colors.primary.main,
marginLeft: spacing.xs,
fontWeight: '500',
fontWeight: '700',
letterSpacing: 0.3,
},
divider: {
height: 1,
backgroundColor: colors.divider,
marginHorizontal: spacing.md,
height: 0.5,
backgroundColor: colors.divider + '60',
marginHorizontal: spacing.lg,
},
section: {
padding: spacing.md,
padding: spacing.lg,
},
sectionHeader: {
flexDirection: 'row',
@@ -462,38 +472,44 @@ function createGroupInfoPanelStyles(colors: AppColors) {
marginBottom: spacing.sm,
},
sectionTitle: {
fontSize: fontSizes.md,
fontWeight: '600',
fontSize: fontSizes.md + 1,
fontWeight: '700',
color: colors.text.primary,
marginLeft: spacing.xs,
letterSpacing: 0.3,
},
noticeBox: {
backgroundColor: colors.chat.tipBg,
backgroundColor: colors.warning.light + '08',
padding: spacing.md,
borderRadius: 8,
borderRadius: borderRadius.lg,
borderLeftWidth: 3,
borderLeftColor: colors.warning.main,
},
noticeText: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
lineHeight: 20,
lineHeight: 22,
fontWeight: '400',
},
noticeTime: {
fontSize: fontSizes.xs,
color: colors.text.hint,
marginTop: spacing.xs,
textAlign: 'right',
fontWeight: '500',
},
descriptionBox: {
backgroundColor: colors.chat.surfaceMuted,
backgroundColor: colors.background.default,
padding: spacing.md,
borderRadius: 8,
borderRadius: borderRadius.lg,
borderWidth: 0.5,
borderColor: colors.divider + '30',
},
description: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
lineHeight: 20,
lineHeight: 22,
fontWeight: '400',
},
memberList: {
gap: spacing.sm,
@@ -511,15 +527,20 @@ function createGroupInfoPanelStyles(colors: AppColors) {
viewAllText: {
fontSize: fontSizes.sm,
color: colors.primary.main,
fontWeight: '600',
},
memberItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: spacing.xs,
paddingVertical: spacing.sm,
paddingHorizontal: spacing.sm,
borderRadius: borderRadius.md,
marginLeft: -spacing.sm,
marginRight: -spacing.sm,
},
memberInfo: {
flex: 1,
marginLeft: spacing.sm,
marginLeft: spacing.md,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
@@ -528,48 +549,55 @@ function createGroupInfoPanelStyles(colors: AppColors) {
fontSize: fontSizes.md,
color: colors.text.primary,
flex: 1,
fontWeight: '600',
},
ownerBadge: {
fontSize: fontSizes.xs,
color: colors.warning.main,
color: colors.background.paper,
marginLeft: spacing.xs,
backgroundColor: colors.warning.light + '30',
backgroundColor: colors.warning.main,
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
borderRadius: borderRadius.sm,
fontWeight: '800',
},
adminBadge: {
fontSize: fontSizes.xs,
color: colors.primary.main,
marginLeft: spacing.xs,
backgroundColor: colors.primary.light + '30',
backgroundColor: colors.primary.light + '20',
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
borderRadius: borderRadius.sm,
fontWeight: '700',
},
moreMembers: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
textAlign: 'center',
paddingVertical: spacing.sm,
fontWeight: '500',
},
infoRow: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: spacing.sm,
borderBottomWidth: 1,
borderBottomColor: colors.chat.borderHairline,
paddingVertical: spacing.md,
borderBottomWidth: 0.5,
borderBottomColor: colors.divider + '40',
},
infoLabel: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
fontWeight: '500',
},
infoValue: {
fontSize: fontSizes.sm,
color: colors.text.primary,
fontWeight: '600',
textAlign: 'right',
},
actionSection: {
padding: spacing.md,
padding: spacing.lg,
paddingBottom: spacing.xl,
},
actionButton: {
@@ -577,24 +605,27 @@ function createGroupInfoPanelStyles(colors: AppColors) {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: spacing.md,
backgroundColor: colors.primary.light + '15',
borderRadius: 12,
backgroundColor: colors.primary.light + '10',
borderRadius: borderRadius.lg,
marginBottom: spacing.sm,
borderWidth: 1,
borderColor: colors.primary.light,
borderWidth: 1.5,
borderColor: colors.primary.light + '50',
},
actionButtonText: {
fontSize: fontSizes.md,
fontWeight: '600',
fontWeight: '700',
color: colors.primary.main,
marginLeft: spacing.sm,
letterSpacing: 0.3,
},
leaveButton: {
backgroundColor: colors.error.light + '15',
borderColor: colors.error.light,
backgroundColor: colors.error.light + '08',
borderColor: colors.error.light + '50',
borderWidth: 1.5,
},
leaveButtonText: {
color: colors.error.main,
fontWeight: '700',
},
});
}

View File

@@ -432,18 +432,34 @@ export function createChatScreenStyles(colors: AppColors) {
transform: [{ translateY: -1 }],
},
// 发送按钮
// 发送按钮 - QQ/微信风格
sendButtonActive: {
width: 38,
height: 30,
borderRadius: 15,
width: 36,
height: 36,
borderRadius: 18, // 正圆形
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
...shadows.md,
// 微信风格:轻微阴影,不夸张
shadowColor: colors.primary.main,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 3,
elevation: 2,
},
sendButtonDisabled: {
opacity: 0.6,
opacity: 0.4,
},
// 发送按钮光泽层(叠加在按钮上模拟渐变)
sendButtonShine: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '50%',
borderTopLeftRadius: 18,
borderTopRightRadius: 18,
backgroundColor: 'rgba(255, 255, 255, 0.08)',
},
// 输入框

View File

@@ -6,7 +6,7 @@
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
import { Animated, StyleSheet, TouchableOpacity, View } from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, shadows, useAppColors, type AppColors } from '../../../theme';
import { spacing, shadows, borderRadius, useAppColors, type AppColors } from '../../../theme';
import {
ConversationResponse,
extractTextFromSegments,
@@ -160,13 +160,14 @@ function createConversationRowStyles(colors: AppColors) {
backgroundColor: colors.background.paper,
marginHorizontal: spacing.md,
marginTop: spacing.sm,
borderRadius: 12,
...shadows.sm,
borderRadius: borderRadius.lg,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
conversationItemSelected: {
backgroundColor: colors.primary.light + '20',
borderColor: colors.primary.main,
borderWidth: 1,
backgroundColor: colors.primary.light + '12',
borderColor: colors.primary.main + '60',
borderWidth: 1.5,
},
avatarContainer: {
position: 'relative',
@@ -174,7 +175,7 @@ function createConversationRowStyles(colors: AppColors) {
systemAvatar: {
width: 50,
height: 50,
borderRadius: 12,
borderRadius: borderRadius.md,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.primary.main,
@@ -195,7 +196,7 @@ function createConversationRowStyles(colors: AppColors) {
},
officialBadge: {
backgroundColor: colors.primary.main,
borderRadius: 4,
borderRadius: borderRadius.sm,
paddingHorizontal: 6,
paddingVertical: 2,
marginRight: spacing.xs,
@@ -203,12 +204,13 @@ function createConversationRowStyles(colors: AppColors) {
officialBadgeText: {
color: colors.primary.contrast,
fontSize: 10,
fontWeight: '600',
fontWeight: '800',
},
userName: {
fontWeight: '600',
fontWeight: '700',
color: colors.text.primary,
fontSize: 16,
letterSpacing: 0.3,
},
groupIcon: {
marginRight: 4,
@@ -217,6 +219,7 @@ function createConversationRowStyles(colors: AppColors) {
fontSize: 12,
color: colors.text.secondary,
marginLeft: 2,
fontWeight: '500',
},
pinnedIcon: {
marginLeft: 6,
@@ -228,7 +231,7 @@ function createConversationRowStyles(colors: AppColors) {
groupAvatarPlaceholder: {
width: 50,
height: 50,
borderRadius: 12,
borderRadius: borderRadius.md,
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
@@ -236,6 +239,7 @@ function createConversationRowStyles(colors: AppColors) {
timeText: {
color: colors.text.secondary,
fontSize: 12,
fontWeight: '500',
},
messageRow: {
flexDirection: 'row',
@@ -245,15 +249,16 @@ function createConversationRowStyles(colors: AppColors) {
flex: 1,
color: colors.text.secondary,
fontSize: 14,
fontWeight: '400',
},
unreadMessageText: {
color: colors.text.primary,
fontWeight: '500',
fontWeight: '600',
},
unreadBadge: {
minWidth: 20,
height: 20,
borderRadius: 10,
minWidth: 22,
height: 22,
borderRadius: 11,
backgroundColor: colors.primary.main,
alignItems: 'center',
justifyContent: 'center',
@@ -263,7 +268,7 @@ function createConversationRowStyles(colors: AppColors) {
unreadBadgeText: {
color: colors.primary.contrast,
fontSize: 12,
fontWeight: '600',
fontWeight: '800',
},
});
}

View File

@@ -4,7 +4,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text } from '../../../components/common';
import { spacing, borderRadius, shadows, useAppColors, type AppColors } from '../../../theme';
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../../theme';
interface GroupInfoSummaryCardProps {
groupName?: string;
@@ -18,10 +18,11 @@ function createGroupRequestSharedStyles(colors: AppColors) {
return StyleSheet.create({
headerCard: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
borderRadius: borderRadius.xl,
padding: spacing.lg,
marginBottom: spacing.md,
...shadows.sm,
borderWidth: 0.5,
borderColor: colors.divider + '40',
},
groupHeader: {
flexDirection: 'row',
@@ -33,7 +34,9 @@ function createGroupRequestSharedStyles(colors: AppColors) {
},
groupName: {
marginBottom: spacing.xs,
fontWeight: '700',
fontWeight: '800',
fontSize: fontSizes.xl + 1,
letterSpacing: 0.3,
},
groupMeta: {
flexDirection: 'row',
@@ -42,6 +45,7 @@ function createGroupRequestSharedStyles(colors: AppColors) {
},
groupNoText: {
marginTop: spacing.xs,
fontWeight: '500',
},
descriptionContainer: {
flexDirection: 'row',
@@ -50,11 +54,14 @@ function createGroupRequestSharedStyles(colors: AppColors) {
borderRadius: borderRadius.lg,
padding: spacing.md,
marginTop: spacing.md,
borderWidth: 0.5,
borderColor: colors.divider + '30',
},
groupDesc: {
marginLeft: spacing.sm,
flex: 1,
lineHeight: 20,
lineHeight: 22,
fontWeight: '400',
},
footer: {
paddingHorizontal: spacing.lg,
@@ -64,16 +71,16 @@ function createGroupRequestSharedStyles(colors: AppColors) {
},
btn: {
flex: 1,
height: 42,
borderRadius: borderRadius.md,
height: 46,
borderRadius: borderRadius.lg,
alignItems: 'center',
justifyContent: 'center',
},
reject: {
marginRight: spacing.sm,
backgroundColor: colors.error.light + '25',
borderWidth: 1,
borderColor: colors.error.light,
backgroundColor: colors.error.light + '12',
borderWidth: 1.5,
borderColor: colors.error.light + '60',
},
approve: {
marginLeft: spacing.sm,