feat(profile): enhance settings and privacy UI with icons and theme picker
Refactor the profile settings and privacy screens to provide a more modern and intuitive user experience. - Add icons to visibility options and privacy settings items - Implement a theme selection dropdown in the settings screen - Restructure privacy settings layout into grouped containers - Move account management items (blocked users, deletion) from general settings to privacy settings - Improve visual hierarchy and spacing in settings screens
This commit is contained in:
@@ -12,31 +12,43 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { authService } from '../../services';
|
||||
import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme';
|
||||
import {
|
||||
spacing,
|
||||
borderRadius,
|
||||
fontSizes,
|
||||
useAppColors,
|
||||
type AppColors,
|
||||
} from '../../theme';
|
||||
import { SimpleHeader, Text } from '../../components/common';
|
||||
import { useResponsive, useResponsiveSpacing } from '../../hooks';
|
||||
import type { PrivacySettingsDTO, VisibilityLevel } from '../../types/dto';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
const CONTENT_MAX_WIDTH = 720;
|
||||
|
||||
const VISIBILITY_OPTIONS: { value: VisibilityLevel; label: string }[] = [
|
||||
{ value: 'everyone', label: '所有人可见' },
|
||||
{ value: 'following', label: '仅关注的人可见' },
|
||||
{ value: 'self', label: '仅自己可见' },
|
||||
const VISIBILITY_OPTIONS: { value: VisibilityLevel; label: string; icon: string }[] = [
|
||||
{ value: 'everyone', label: '所有人可见', icon: 'earth' },
|
||||
{ value: 'following', label: '仅关注的人可见', icon: 'account-group-outline' },
|
||||
{ value: 'self', label: '仅自己可见', icon: 'lock-outline' },
|
||||
];
|
||||
|
||||
const PRIVACY_ITEMS: { key: keyof PrivacySettingsDTO; title: string; description: string }[] = [
|
||||
{ key: 'followers_visibility', title: '粉丝列表', description: '谁可以查看你的粉丝列表' },
|
||||
{ key: 'following_visibility', title: '关注列表', description: '谁可以查看你的关注列表' },
|
||||
{ key: 'posts_visibility', title: '帖子列表', description: '谁可以查看你的帖子列表' },
|
||||
{ key: 'favorites_visibility', title: '收藏列表', description: '谁可以查看你的收藏列表' },
|
||||
const PRIVACY_ITEMS: { key: keyof PrivacySettingsDTO; title: string; description: string; icon: string }[] = [
|
||||
{ key: 'followers_visibility', title: '粉丝列表', description: '谁可以查看你的粉丝列表', icon: 'account-group' },
|
||||
{ key: 'following_visibility', title: '关注列表', description: '谁可以查看你的关注列表', icon: 'account-heart' },
|
||||
{ key: 'posts_visibility', title: '帖子列表', description: '谁可以查看你的帖子列表', icon: 'text-box-outline' },
|
||||
{ key: 'favorites_visibility', title: '收藏列表', description: '谁可以查看你的收藏列表', icon: 'star-outline' },
|
||||
];
|
||||
|
||||
const PRIVACY_NAV_ITEMS: { key: string; title: string; description: string; icon: string; danger?: boolean }[] = [
|
||||
{ key: 'blocked_users', title: '黑名单', description: '管理你屏蔽的用户', icon: 'account-off-outline' },
|
||||
{ key: 'account_deletion', title: '注销账号', description: '永久删除你的账号', icon: 'account-remove-outline', danger: true },
|
||||
];
|
||||
|
||||
function createPrivacySettingsStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.paper,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
loadingWrap: {
|
||||
flex: 1,
|
||||
@@ -44,56 +56,68 @@ function createPrivacySettingsStyles(colors: AppColors) {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
scrollContent: {
|
||||
paddingVertical: spacing.lg,
|
||||
paddingVertical: spacing.md,
|
||||
},
|
||||
content: {
|
||||
maxWidth: CONTENT_MAX_WIDTH,
|
||||
alignSelf: 'center',
|
||||
width: '100%',
|
||||
},
|
||||
section: {
|
||||
marginBottom: spacing['2xl'],
|
||||
groupHeader: {
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingVertical: spacing.sm,
|
||||
},
|
||||
sectionHeader: {
|
||||
paddingHorizontal: spacing['2xl'],
|
||||
marginBottom: spacing.sm,
|
||||
marginTop: spacing.sm,
|
||||
},
|
||||
sectionTitle: {
|
||||
groupTitle: {
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.sm,
|
||||
color: colors.text.secondary,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
// 扁平式布局:整行横向排列,底部边框分隔
|
||||
groupContainer: {
|
||||
backgroundColor: colors.background.paper,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.divider,
|
||||
overflow: 'visible',
|
||||
},
|
||||
item: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: spacing.lg,
|
||||
marginHorizontal: spacing['2xl'],
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: spacing.lg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.divider,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
itemLast: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
itemTextWrap: {
|
||||
itemLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: borderRadius.md,
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
dangerIconContainer: {
|
||||
backgroundColor: colors.error.light + '20',
|
||||
},
|
||||
itemContent: {
|
||||
flex: 1,
|
||||
},
|
||||
itemTitle: {
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.md,
|
||||
color: colors.text.primary,
|
||||
marginBottom: spacing.xs,
|
||||
fontWeight: '500',
|
||||
},
|
||||
itemDescription: {
|
||||
fontSize: fontSizes.sm,
|
||||
color: colors.text.secondary,
|
||||
marginTop: 1,
|
||||
},
|
||||
// 无背景色的选择框,紧贴右侧
|
||||
selectBox: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -104,10 +128,9 @@ function createPrivacySettingsStyles(colors: AppColors) {
|
||||
color: colors.text.secondary,
|
||||
marginRight: spacing.xs,
|
||||
},
|
||||
// 原位展开的下拉菜单
|
||||
dropdownMenu: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
right: spacing.lg,
|
||||
top: '100%',
|
||||
marginTop: 4,
|
||||
backgroundColor: colors.background.paper,
|
||||
@@ -120,13 +143,17 @@ function createPrivacySettingsStyles(colors: AppColors) {
|
||||
shadowRadius: 4,
|
||||
elevation: 4,
|
||||
zIndex: 100,
|
||||
minWidth: 160,
|
||||
minWidth: 180,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
dropdownOption: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
paddingVertical: spacing.md,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.divider,
|
||||
gap: spacing.sm,
|
||||
},
|
||||
dropdownOptionLast: {
|
||||
borderBottomWidth: 0,
|
||||
@@ -139,6 +166,22 @@ function createPrivacySettingsStyles(colors: AppColors) {
|
||||
color: colors.primary.main,
|
||||
fontWeight: '600',
|
||||
},
|
||||
dropdownOptionIcon: {
|
||||
width: 22,
|
||||
alignItems: 'center',
|
||||
},
|
||||
navItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: spacing.lg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.divider,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
navItemLast: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,7 +195,7 @@ export const PrivacySettingsScreen: React.FC = () => {
|
||||
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 responsivePadding = useResponsiveSpacing({ xs: 0, sm: 0, md: 0, lg: 24, xl: 32 });
|
||||
|
||||
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
|
||||
|
||||
@@ -216,42 +259,52 @@ export const PrivacySettingsScreen: React.FC = () => {
|
||||
<SimpleHeader title="隐私设置" onBack={() => router.back()} />
|
||||
<ScrollView contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset, paddingHorizontal: responsivePadding }]}>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.section}>
|
||||
{/* 可见性设置分组 */}
|
||||
<View style={styles.groupHeader}>
|
||||
<Text variant="caption" style={styles.groupTitle}>
|
||||
可见性设置
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.groupContainer, { overflow: 'visible', zIndex: openPicker ? 10 : 1 }]}>
|
||||
{PRIVACY_ITEMS.map((item, index) => {
|
||||
const isOpen = openPicker === item.key;
|
||||
const isLast = index === PRIVACY_ITEMS.length - 1;
|
||||
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>
|
||||
|
||||
<View key={item.key} style={{ overflow: 'visible', zIndex: isOpen ? 10 : 1 }}>
|
||||
<TouchableOpacity
|
||||
style={styles.selectBox}
|
||||
style={[styles.item, !isOpen && isLast && styles.itemLast]}
|
||||
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}
|
||||
/>
|
||||
<View style={styles.itemLeft}>
|
||||
<View style={styles.iconContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name={item.icon as any}
|
||||
size={18}
|
||||
color={colors.text.secondary}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.itemContent}>
|
||||
<Text variant="body" style={styles.itemTitle}>
|
||||
{item.title}
|
||||
</Text>
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.itemDescription}>
|
||||
{item.description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.selectBox}>
|
||||
<Text style={styles.selectBoxText}>
|
||||
{getLabel(settings?.[item.key])}
|
||||
</Text>
|
||||
<MaterialCommunityIcons
|
||||
name={isOpen ? 'chevron-up' : 'chevron-down'}
|
||||
size={16}
|
||||
color={colors.text.secondary}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* 原位展开的下拉选项 */}
|
||||
{isOpen && (
|
||||
<View style={styles.dropdownMenu}>
|
||||
{VISIBILITY_OPTIONS.map((option, optIndex) => {
|
||||
@@ -269,11 +322,18 @@ export const PrivacySettingsScreen: React.FC = () => {
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.dropdownOptionIcon}>
|
||||
<MaterialCommunityIcons
|
||||
name={option.icon as any}
|
||||
size={16}
|
||||
color={isActive ? colors.primary.main : colors.text.secondary}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
variant="body"
|
||||
style={[
|
||||
styles.dropdownOptionText,
|
||||
isActive ? styles.dropdownOptionTextActive : undefined,
|
||||
isActive && styles.dropdownOptionTextActive,
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
@@ -287,6 +347,49 @@ export const PrivacySettingsScreen: React.FC = () => {
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* 账号管理分组 */}
|
||||
<View style={[styles.groupHeader, { marginTop: spacing.xl }]}>
|
||||
<Text variant="caption" style={styles.groupTitle}>
|
||||
账号管理
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.groupContainer}>
|
||||
{PRIVACY_NAV_ITEMS.map((item, index) => (
|
||||
<TouchableOpacity
|
||||
key={item.key}
|
||||
style={[styles.navItem, index === PRIVACY_NAV_ITEMS.length - 1 && styles.navItemLast]}
|
||||
onPress={() => {
|
||||
if (item.key === 'blocked_users') router.push(hrefs.hrefProfileBlocked());
|
||||
else if (item.key === 'account_deletion') router.push(hrefs.hrefProfileDeletion());
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.itemLeft}>
|
||||
<View style={[styles.iconContainer, item.danger && styles.dangerIconContainer]}>
|
||||
<MaterialCommunityIcons
|
||||
name={item.icon as any}
|
||||
size={18}
|
||||
color={item.danger ? colors.error.main : colors.text.secondary}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.itemContent}>
|
||||
<Text
|
||||
variant="body"
|
||||
color={item.danger ? colors.error.main : colors.text.primary}
|
||||
style={styles.itemTitle}
|
||||
>
|
||||
{item.title}
|
||||
</Text>
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.itemDescription}>
|
||||
{item.description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 采用扁平化设计,全宽布局,简洁分割线
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
@@ -34,6 +34,12 @@ import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
const APP_VERSION = Constants.expoConfig?.version || '1.0.0';
|
||||
|
||||
const THEME_OPTIONS: { value: 'system' | 'light' | 'dark'; label: string }[] = [
|
||||
{ value: 'system', label: '跟随系统' },
|
||||
{ value: 'light', label: '浅色' },
|
||||
{ value: 'dark', label: '深色' },
|
||||
];
|
||||
|
||||
interface SettingsItem {
|
||||
key: string;
|
||||
title: string;
|
||||
@@ -42,6 +48,7 @@ interface SettingsItem {
|
||||
showArrow?: boolean;
|
||||
danger?: boolean;
|
||||
subtitle?: string;
|
||||
isThemePicker?: boolean;
|
||||
}
|
||||
|
||||
const SETTINGS_GROUPS_BASE = [
|
||||
@@ -52,8 +59,6 @@ const SETTINGS_GROUPS_BASE = [
|
||||
{ key: 'verification', title: '身份认证', icon: 'check-decagram', showArrow: true, subtitle: '获得认证标识,解锁更多功能' },
|
||||
{ key: 'privacy', title: '隐私设置', icon: 'shield-account-outline', showArrow: true },
|
||||
{ key: 'security', title: '账号安全', icon: 'lock-outline', showArrow: true },
|
||||
{ key: 'blocked_users', title: '黑名单', icon: 'account-off-outline', showArrow: true },
|
||||
{ key: 'account_deletion', title: '注销账号', icon: 'account-remove-outline', showArrow: true, danger: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -87,6 +92,8 @@ function createSettingsStyles(colors: AppColors) {
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.divider,
|
||||
overflow: 'visible',
|
||||
zIndex: 1,
|
||||
},
|
||||
groupHeader: {
|
||||
paddingHorizontal: spacing.lg,
|
||||
@@ -134,6 +141,53 @@ function createSettingsStyles(colors: AppColors) {
|
||||
subtitle: {
|
||||
marginTop: 2,
|
||||
},
|
||||
// 无背景色的选择框,紧贴右侧
|
||||
selectBox: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: spacing.xs,
|
||||
},
|
||||
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: 120,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
dropdownOption: {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.divider,
|
||||
},
|
||||
dropdownOptionLast: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
dropdownOptionText: {
|
||||
fontSize: fontSizes.sm,
|
||||
color: colors.text.primary,
|
||||
},
|
||||
dropdownOptionTextActive: {
|
||||
color: colors.primary.main,
|
||||
fontWeight: '600',
|
||||
},
|
||||
logoutButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -172,12 +226,11 @@ export const SettingsScreen: React.FC = () => {
|
||||
const styles = useMemo(() => createSettingsStyles(colors), [colors]);
|
||||
const themePreference = useThemePreference();
|
||||
const setThemePreference = useSetThemePreference();
|
||||
const [openPicker, setOpenPicker] = useState<string | null>(null);
|
||||
|
||||
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
|
||||
|
||||
const settingsGroups = useMemo(() => {
|
||||
const themeSubtitle =
|
||||
themePreference === 'system' ? '跟随系统' : themePreference === 'dark' ? '深色' : '浅色';
|
||||
return [
|
||||
{
|
||||
title: '个性化',
|
||||
@@ -193,8 +246,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
key: 'theme',
|
||||
title: '主题模式',
|
||||
icon: 'theme-light-dark',
|
||||
showArrow: true,
|
||||
subtitle: themeSubtitle,
|
||||
isThemePicker: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -208,31 +260,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
router.push(hrefs.hrefProfileChatSettings());
|
||||
break;
|
||||
case 'theme': {
|
||||
Alert.alert(
|
||||
'主题模式',
|
||||
'选择应用界面明暗',
|
||||
[
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{
|
||||
text: '跟随系统',
|
||||
onPress: () => {
|
||||
void setThemePreference('system');
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '浅色',
|
||||
onPress: () => {
|
||||
void setThemePreference('light');
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '深色',
|
||||
onPress: () => {
|
||||
void setThemePreference('dark');
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
setOpenPicker(openPicker === 'theme' ? null : 'theme');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -289,12 +317,19 @@ export const SettingsScreen: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderSettingItem = (item: SettingsItem, index: number, total: number) => (
|
||||
const getThemeLabel = (value: string) =>
|
||||
THEME_OPTIONS.find((o) => o.value === value)?.label ?? '跟随系统';
|
||||
|
||||
const renderSettingItem = (item: SettingsItem, index: number, total: number) => {
|
||||
const isThemeOpen = item.isThemePicker && openPicker === 'theme';
|
||||
const themeLabel = item.isThemePicker ? getThemeLabel(themePreference) : undefined;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={item.key}
|
||||
style={[
|
||||
styles.settingItem,
|
||||
index === total - 1 && styles.settingItemLast,
|
||||
index === total - 1 && !isThemeOpen && styles.settingItemLast,
|
||||
]}
|
||||
onPress={() => (item.onPress ? item.onPress() : handleItemPress(item.key))}
|
||||
activeOpacity={0.7}
|
||||
@@ -318,14 +353,64 @@ export const SettingsScreen: React.FC = () => {
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
{item.showArrow && (
|
||||
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
||||
{item.isThemePicker ? (
|
||||
<View style={{ position: 'relative' }}>
|
||||
<View style={styles.selectBox}>
|
||||
<Text style={styles.selectBoxText}>{themeLabel}</Text>
|
||||
<MaterialCommunityIcons
|
||||
name={isThemeOpen ? 'chevron-up' : 'chevron-down'}
|
||||
size={18}
|
||||
color={colors.text.secondary}
|
||||
/>
|
||||
</View>
|
||||
{isThemeOpen && (
|
||||
<View style={styles.dropdownMenu}>
|
||||
{THEME_OPTIONS.map((option, optIndex) => {
|
||||
const isActive = themePreference === option.value;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={option.value}
|
||||
style={[
|
||||
styles.dropdownOption,
|
||||
optIndex === THEME_OPTIONS.length - 1 && styles.dropdownOptionLast,
|
||||
]}
|
||||
onPress={() => {
|
||||
void setThemePreference(option.value);
|
||||
setOpenPicker(null);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text
|
||||
variant="body"
|
||||
style={[
|
||||
styles.dropdownOptionText,
|
||||
isActive ? styles.dropdownOptionTextActive : undefined,
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
item.showArrow && (
|
||||
<MaterialCommunityIcons name="chevron-right" size={20} color={colors.text.hint} />
|
||||
)
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const renderGroup = (group: (typeof settingsGroups)[0]) => (
|
||||
<View key={group.title}>
|
||||
const renderGroup = (group: (typeof settingsGroups)[0], groupIndex: number) => (
|
||||
<View
|
||||
key={group.title}
|
||||
style={[
|
||||
{ overflow: 'visible', zIndex: group.items.some((i) => i.isThemePicker && openPicker === 'theme') ? 10 : groupIndex },
|
||||
]}
|
||||
>
|
||||
<View style={styles.groupHeader}>
|
||||
<Text variant="caption" style={styles.groupTitle}>
|
||||
{group.title}
|
||||
@@ -352,7 +437,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
|
||||
const renderContent = () => (
|
||||
<>
|
||||
{settingsGroups.map((group) => renderGroup(group))}
|
||||
{settingsGroups.map((group, index) => renderGroup(group, index))}
|
||||
{renderLogoutButton()}
|
||||
<View style={styles.footer}>
|
||||
<Text variant="caption" color={colors.text.hint}>
|
||||
|
||||
Reference in New Issue
Block a user