refactor(ui): modernize components with flat design and UX refinements
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 42s
Frontend CI / build-android-apk (push) Failing after 7m16s
Frontend CI / ota-android (push) Successful in 10m29s

- Redesign chat screen header with improved layout and panel styling
- Update emoji panel tab bar from emoji to icon-based navigation
- Simplify trade detail view with inline content attributes
- Add masonry layout support to market view grid
- Refactor privacy settings from card-based to list-based dropdown UI
- Improve comment item actions layout and styling
- Update trade creation flow with buy/sell specific text
- Adjust padding, gaps, and font sizes across multiple components
- Clean up unused code and imports
This commit is contained in:
lafay
2026-04-27 01:02:39 +08:00
parent e519346261
commit 0e2945b86b
15 changed files with 535 additions and 384 deletions

View File

@@ -524,9 +524,6 @@ export const AboutScreen: React.FC = () => {
>
<View style={styles.content}>
<View style={styles.header}>
<View style={styles.logoContainer}>
<Text style={styles.logoText}></Text>
</View>
<Text style={styles.appName}>{APP_NAME}</Text>
<Text style={styles.appSlogan}>{APP_SLOGAN}</Text>
<Text style={styles.versionText}> {APP_VERSION}</Text>

View File

@@ -52,12 +52,8 @@ const PRIVACY_SECTIONS = [
1.2 我们在您使用服务时收集的信息
• 设备信息包括设备型号、操作系统版本、设备设置、唯一设备标识符如IMEI、Android ID、IDFA、OAID、MAC地址等、IP地址等
• 日志信息IP地址、访问时间、浏览记录、操作日志等
• 位置信息:为了向您提供"校园圈"、"附近的人"等基于位置的服务,经您授权,我们会收集您的精确地理位置信息。该信息属于敏感个人信息,拒绝提供仅会影响相关功能,不影响其他服务的使用
• 相机/相册权限:用于发布动态、更换头像。我们仅在您主动触发相关功能时申请权限,且不会在后台持续收集
• 通讯录权限:经您授权后用于查找好友
1.3 从第三方获取的信息
如您使用第三方账号登录功能(具体以应用实际提供的登录方式为准),我们会从第三方获取您授权共享的账号信息(如昵称、头像等)。`,
• 通讯录权限:经您授权后用于查找好友`,
},
{
title: '二、我们如何使用信息',

View File

@@ -18,10 +18,10 @@ import type { PrivacySettingsDTO, VisibilityLevel } from '../../types/dto';
const CONTENT_MAX_WIDTH = 720;
const VISIBILITY_OPTIONS: { value: VisibilityLevel; label: string; description: string }[] = [
{ value: 'everyone', label: '所有人可见', description: '任何人都可查看' },
{ value: 'following', label: '仅关注的人可见', description: '只有你关注的人可查看' },
{ value: 'self', label: '仅自己可见', description: '只有自己可查看' },
const VISIBILITY_OPTIONS: { value: VisibilityLevel; label: string }[] = [
{ value: 'everyone', label: '所有人可见' },
{ value: 'following', label: '仅关注的人可见' },
{ value: 'self', label: '仅自己可见' },
];
const PRIVACY_ITEMS: { key: keyof PrivacySettingsDTO; title: string; description: string }[] = [
@@ -65,55 +65,78 @@ function createPrivacySettingsStyles(colors: AppColors) {
textTransform: 'uppercase',
letterSpacing: 0.5,
},
// 隐私设置项 - 扁平化卡片风格
// 扁平式布局:整行横向排列,底部边框分隔
item: {
backgroundColor: colors.background.default,
borderRadius: 14,
padding: spacing.lg,
marginHorizontal: spacing['2xl'],
marginBottom: spacing.md,
},
itemHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: spacing.sm,
paddingVertical: spacing.lg,
marginHorizontal: spacing['2xl'],
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.divider,
},
itemLast: {
borderBottomWidth: 0,
},
itemTextWrap: {
flex: 1,
marginRight: spacing.md,
},
itemTitle: {
fontWeight: '600',
fontSize: fontSizes.md,
color: colors.text.primary,
marginBottom: spacing.xs,
},
itemDescription: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
marginBottom: spacing.md,
},
optionsRow: {
// 无背景色的选择框,紧贴右侧
selectBox: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
alignItems: 'center',
paddingVertical: spacing.xs,
},
// 选项按钮 - 扁平化风格
optionButton: {
selectBoxText: {
fontSize: fontSizes.sm,
color: colors.text.secondary,
marginRight: spacing.xs,
},
// 原位展开的下拉菜单
dropdownMenu: {
position: 'absolute',
right: 0,
top: '100%',
marginTop: 4,
backgroundColor: colors.background.paper,
borderRadius: borderRadius.md,
borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.divider,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 4,
zIndex: 100,
minWidth: 160,
},
dropdownOption: {
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm,
borderRadius: borderRadius.full,
borderWidth: 1,
borderColor: colors.divider,
backgroundColor: colors.background.paper,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.divider,
},
optionButtonActive: {
backgroundColor: colors.primary.main,
borderColor: colors.primary.main,
dropdownOptionLast: {
borderBottomWidth: 0,
},
optionText: {
fontSize: 13,
dropdownOptionText: {
fontSize: fontSizes.sm,
color: colors.text.primary,
},
optionTextActive: {
color: colors.text.inverse,
fontWeight: '500',
dropdownOptionTextActive: {
color: colors.primary.main,
fontWeight: '600',
},
});
}
@@ -127,6 +150,7 @@ export const PrivacySettingsScreen: React.FC = () => {
const [settings, setSettings] = useState<PrivacySettingsDTO | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [openPicker, setOpenPicker] = useState<keyof PrivacySettingsDTO | null>(null);
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
@@ -150,10 +174,10 @@ export const PrivacySettingsScreen: React.FC = () => {
const updateSetting = useCallback(async (key: keyof PrivacySettingsDTO, value: VisibilityLevel) => {
if (!settings) return;
const newSettings = { ...settings, [key]: value };
setSettings(newSettings);
setSaving(true);
try {
const ok = await authService.updatePrivacySettings({
@@ -171,6 +195,9 @@ export const PrivacySettingsScreen: React.FC = () => {
}
}, [settings]);
const getLabel = (value?: VisibilityLevel) =>
VISIBILITY_OPTIONS.find((o) => o.value === value)?.label ?? '请选择';
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
@@ -186,42 +213,75 @@ export const PrivacySettingsScreen: React.FC = () => {
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
<View style={styles.content}>
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text variant="caption" style={styles.sectionTitle}>
</Text>
</View>
{PRIVACY_ITEMS.map((item) => (
<View key={item.key} style={styles.item}>
<Text variant="body" style={styles.itemTitle}>
{item.title}
</Text>
<Text variant="caption" color={colors.text.secondary} style={styles.itemDescription}>
{item.description}
</Text>
<View style={styles.optionsRow}>
{VISIBILITY_OPTIONS.map((option) => {
const isActive = settings?.[item.key] === option.value;
return (
<TouchableOpacity
key={option.value}
style={[styles.optionButton, isActive && styles.optionButtonActive]}
onPress={() => updateSetting(item.key, option.value)}
activeOpacity={0.7}
>
<Text
variant="caption"
style={[styles.optionText, isActive ? styles.optionTextActive : {}]}
>
{option.label}
</Text>
</TouchableOpacity>
);
})}
{PRIVACY_ITEMS.map((item, index) => {
const isOpen = openPicker === item.key;
return (
<View
key={item.key}
style={[
styles.item,
index === PRIVACY_ITEMS.length - 1 && styles.itemLast,
]}
>
<View style={styles.itemTextWrap}>
<Text variant="body" style={styles.itemTitle}>
{item.title}
</Text>
<Text variant="caption" color={colors.text.secondary} style={styles.itemDescription}>
{item.description}
</Text>
</View>
<TouchableOpacity
style={styles.selectBox}
onPress={() => setOpenPicker(isOpen ? null : item.key)}
activeOpacity={0.7}
>
<Text style={styles.selectBoxText}>
{getLabel(settings?.[item.key])}
</Text>
<MaterialCommunityIcons
name={isOpen ? 'chevron-up' : 'chevron-down'}
size={18}
color={colors.text.secondary}
/>
</TouchableOpacity>
{/* 原位展开的下拉选项 */}
{isOpen && (
<View style={styles.dropdownMenu}>
{VISIBILITY_OPTIONS.map((option, optIndex) => {
const isActive = settings?.[item.key] === option.value;
return (
<TouchableOpacity
key={option.value}
style={[
styles.dropdownOption,
optIndex === VISIBILITY_OPTIONS.length - 1 && styles.dropdownOptionLast,
]}
onPress={() => {
updateSetting(item.key, option.value);
setOpenPicker(null);
}}
activeOpacity={0.7}
>
<Text
variant="body"
style={[
styles.dropdownOptionText,
isActive ? styles.dropdownOptionTextActive : undefined,
]}
>
{option.label}
</Text>
</TouchableOpacity>
);
})}
</View>
)}
</View>
</View>
))}
);
})}
</View>
</View>
</ScrollView>