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