Initial frontend repository commit.

Include app source and update .gitignore to exclude local release artifacts and signing files.

Made-with: Cursor
This commit is contained in:
2026-03-09 21:29:03 +08:00
commit 3968660048
129 changed files with 55599 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
import React from 'react';
import { View, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text } from '../../../components/common';
import { colors, spacing, borderRadius, shadows } from '../../../theme';
interface GroupInfoSummaryCardProps {
groupName?: string;
groupAvatar?: string;
groupNo?: string;
groupDescription?: string;
memberCountText?: string;
}
export const GroupInfoSummaryCard: React.FC<GroupInfoSummaryCardProps> = ({
groupName,
groupAvatar,
groupNo,
groupDescription,
memberCountText,
}) => {
return (
<View style={styles.headerCard}>
<View style={styles.groupHeader}>
<Avatar source={groupAvatar || ''} size={88} name={groupName} />
<View style={styles.groupInfo}>
<Text variant="h3" style={styles.groupName} numberOfLines={1}>{groupName || '群聊'}</Text>
<View style={styles.groupMeta}>
<MaterialCommunityIcons name="account-group" size={16} color={colors.text.secondary} />
<Text variant="caption" color={colors.text.secondary}>
{memberCountText || '- 人'}
</Text>
</View>
<Text variant="caption" color={colors.text.secondary} style={styles.groupNoText}>
{groupNo || '-'}
</Text>
</View>
</View>
<View style={styles.descriptionContainer}>
<MaterialCommunityIcons name="information-outline" size={16} color={colors.text.secondary} />
<Text variant="body" color={colors.text.secondary} style={styles.groupDesc}>
{groupDescription || '暂无群描述'}
</Text>
</View>
</View>
);
};
interface DecisionFooterProps {
canAction: boolean;
submitting: boolean;
onReject: () => void;
onApprove: () => void;
processedText?: string;
}
export const DecisionFooter: React.FC<DecisionFooterProps> = ({
canAction,
submitting,
onReject,
onApprove,
processedText = '该请求已处理',
}) => {
const insets = useSafeAreaInsets();
if (canAction) {
return (
<View style={[styles.footer, { paddingBottom: Math.max(insets.bottom, spacing.sm) }]}>
<TouchableOpacity style={[styles.btn, styles.reject]} onPress={onReject} disabled={submitting}>
<Text variant="body" color={colors.error.main}></Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.btn, styles.approve]} onPress={onApprove} disabled={submitting}>
{submitting ? <ActivityIndicator color="#fff" /> : <Text variant="body" color="#fff"></Text>}
</TouchableOpacity>
</View>
);
}
return (
<View style={[styles.statusBox, { paddingBottom: Math.max(insets.bottom, spacing.sm) }]}>
<Text variant="caption" color={colors.text.secondary}>{processedText}</Text>
</View>
);
};
const styles = StyleSheet.create({
headerCard: {
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
padding: spacing.lg,
marginBottom: spacing.md,
...shadows.sm,
},
groupHeader: {
flexDirection: 'row',
alignItems: 'center',
},
groupInfo: {
marginLeft: spacing.lg,
flex: 1,
},
groupName: {
marginBottom: spacing.xs,
fontWeight: '700',
},
groupMeta: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
},
groupNoText: {
marginTop: spacing.xs,
},
descriptionContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
backgroundColor: colors.background.default,
borderRadius: borderRadius.lg,
padding: spacing.md,
marginTop: spacing.md,
},
groupDesc: {
marginLeft: spacing.sm,
flex: 1,
lineHeight: 20,
},
footer: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.sm,
flexDirection: 'row',
backgroundColor: colors.background.default,
},
btn: {
flex: 1,
height: 42,
borderRadius: borderRadius.md,
alignItems: 'center',
justifyContent: 'center',
},
reject: {
marginRight: spacing.sm,
backgroundColor: colors.error.light + '25',
borderWidth: 1,
borderColor: colors.error.light,
},
approve: {
marginLeft: spacing.sm,
backgroundColor: colors.primary.main,
},
statusBox: {
alignItems: 'center',
paddingTop: spacing.sm,
backgroundColor: colors.background.default,
},
});