feat(Theme): enhance theming and UI consistency across components
- Updated app.json to set userInterfaceStyle to automatic for improved theme adaptability. - Refactored layout components to utilize useAppColors for dynamic theming, ensuring consistent color usage. - Introduced SystemChrome component to manage system UI background color based on theme. - Enhanced TabsLayout, ProfileStackLayout, and other components to leverage new theming structure. - Improved QRCodeScanner, SearchBar, and CommentItem styles to align with the updated theme system. - Consolidated styles in SystemMessageItem and TabBar for better maintainability and visual coherence.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* 在宽屏下居中显示,最大宽度限制
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
@@ -16,7 +16,15 @@ import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import Constants from 'expo-constants';
|
||||
import { colors, spacing, fontSizes, borderRadius } from '../../theme';
|
||||
import {
|
||||
useAppColors,
|
||||
spacing,
|
||||
fontSizes,
|
||||
borderRadius,
|
||||
useThemePreference,
|
||||
useSetThemePreference,
|
||||
type AppColors,
|
||||
} from '../../theme';
|
||||
import { useAuthStore } from '../../stores';
|
||||
import { Text, ResponsiveContainer } from '../../components/common';
|
||||
import { useResponsive } from '../../hooks';
|
||||
@@ -34,11 +42,9 @@ interface SettingsItem {
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
// 获取应用版本号
|
||||
const APP_VERSION = Constants.expoConfig?.version || '1.0.0';
|
||||
|
||||
// 设置分组配置
|
||||
const SETTINGS_GROUPS = [
|
||||
const SETTINGS_GROUPS_BASE = [
|
||||
{
|
||||
title: '账号与安全',
|
||||
icon: 'shield-check-outline',
|
||||
@@ -66,19 +72,161 @@ const SETTINGS_GROUPS = [
|
||||
},
|
||||
];
|
||||
|
||||
function createSettingsStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingVertical: spacing.md,
|
||||
},
|
||||
groupContainer: {
|
||||
marginBottom: spacing.lg,
|
||||
},
|
||||
groupHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: spacing.lg,
|
||||
marginBottom: spacing.sm,
|
||||
gap: spacing.xs,
|
||||
},
|
||||
groupTitle: {
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.sm,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: colors.background.paper,
|
||||
marginHorizontal: spacing.lg,
|
||||
borderRadius: borderRadius.lg,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
settingItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: spacing.md,
|
||||
paddingHorizontal: spacing.md,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.divider,
|
||||
},
|
||||
settingItemFirst: {
|
||||
borderTopLeftRadius: borderRadius.lg,
|
||||
borderTopRightRadius: borderRadius.lg,
|
||||
},
|
||||
settingItemLast: {
|
||||
borderBottomLeftRadius: borderRadius.lg,
|
||||
borderBottomRightRadius: borderRadius.lg,
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
settingItemLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: borderRadius.md,
|
||||
backgroundColor: colors.primary.light + '20',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
dangerIconContainer: {
|
||||
backgroundColor: colors.error.light + '20',
|
||||
},
|
||||
settingContent: {
|
||||
flex: 1,
|
||||
},
|
||||
subtitle: {
|
||||
marginTop: 2,
|
||||
},
|
||||
logoutButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.background.paper,
|
||||
marginHorizontal: spacing.lg,
|
||||
marginTop: spacing.sm,
|
||||
marginBottom: spacing.lg,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: borderRadius.lg,
|
||||
gap: spacing.sm,
|
||||
},
|
||||
logoutText: {
|
||||
fontWeight: '500',
|
||||
},
|
||||
footer: {
|
||||
alignItems: 'center',
|
||||
marginTop: spacing.xl,
|
||||
paddingBottom: spacing.xl,
|
||||
},
|
||||
copyright: {
|
||||
marginTop: spacing.xs,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const SettingsScreen: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const { logout } = useAuthStore();
|
||||
const { isWideScreen } = useResponsive();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { isMobile } = useResponsive();
|
||||
|
||||
// 底部间距,避免被 TabBar 遮挡
|
||||
const colors = useAppColors();
|
||||
const styles = useMemo(() => createSettingsStyles(colors), [colors]);
|
||||
const themePreference = useThemePreference();
|
||||
const setThemePreference = useSetThemePreference();
|
||||
|
||||
const scrollBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
|
||||
|
||||
// 处理设置项点击
|
||||
const settingsGroups = useMemo(() => {
|
||||
const themeSubtitle =
|
||||
themePreference === 'system' ? '跟随系统' : themePreference === 'dark' ? '深色' : '浅色';
|
||||
return [
|
||||
{
|
||||
title: '显示与外观',
|
||||
icon: 'palette-outline',
|
||||
items: [
|
||||
{
|
||||
key: 'theme',
|
||||
title: '主题模式',
|
||||
icon: 'theme-light-dark',
|
||||
showArrow: true,
|
||||
subtitle: themeSubtitle,
|
||||
},
|
||||
],
|
||||
},
|
||||
...SETTINGS_GROUPS_BASE,
|
||||
];
|
||||
}, [themePreference]);
|
||||
|
||||
const handleItemPress = (key: string) => {
|
||||
switch (key) {
|
||||
case 'theme':
|
||||
Alert.alert('主题模式', '选择应用界面明暗', [
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{
|
||||
text: '跟随系统',
|
||||
onPress: () => {
|
||||
void setThemePreference('system');
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '浅色',
|
||||
onPress: () => {
|
||||
void setThemePreference('light');
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '深色',
|
||||
onPress: () => {
|
||||
void setThemePreference('dark');
|
||||
},
|
||||
},
|
||||
]);
|
||||
break;
|
||||
case 'edit_profile':
|
||||
router.push(hrefs.hrefProfileEdit());
|
||||
break;
|
||||
@@ -103,7 +251,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
onPress: async () => {
|
||||
await logout();
|
||||
router.replace(hrefs.hrefAuthLogin());
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -113,7 +261,6 @@ export const SettingsScreen: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染单个设置项
|
||||
const renderSettingItem = (item: SettingsItem, index: number, total: number) => (
|
||||
<TouchableOpacity
|
||||
key={item.key}
|
||||
@@ -122,14 +269,11 @@ export const SettingsScreen: React.FC = () => {
|
||||
index === 0 && styles.settingItemFirst,
|
||||
index === total - 1 && styles.settingItemLast,
|
||||
]}
|
||||
onPress={() => item.onPress ? item.onPress() : handleItemPress(item.key)}
|
||||
onPress={() => (item.onPress ? item.onPress() : handleItemPress(item.key))}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.settingItemLeft}>
|
||||
<View style={[
|
||||
styles.iconContainer,
|
||||
item.danger && styles.dangerIconContainer
|
||||
]}>
|
||||
<View style={[styles.iconContainer, item.danger && styles.dangerIconContainer]}>
|
||||
<MaterialCommunityIcons
|
||||
name={item.icon as any}
|
||||
size={20}
|
||||
@@ -137,10 +281,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.settingContent}>
|
||||
<Text
|
||||
variant="body"
|
||||
color={item.danger ? colors.error.main : colors.text.primary}
|
||||
>
|
||||
<Text variant="body" color={item.danger ? colors.error.main : colors.text.primary}>
|
||||
{item.title}
|
||||
</Text>
|
||||
{item.subtitle && (
|
||||
@@ -156,8 +297,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
// 渲染分组
|
||||
const renderGroup = (group: typeof SETTINGS_GROUPS[0], groupIndex: number) => (
|
||||
const renderGroup = (group: (typeof settingsGroups)[0]) => (
|
||||
<View key={group.title} style={styles.groupContainer}>
|
||||
<View style={styles.groupHeader}>
|
||||
<MaterialCommunityIcons name={group.icon as any} size={16} color={colors.primary.main} />
|
||||
@@ -166,14 +306,11 @@ export const SettingsScreen: React.FC = () => {
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
{group.items.map((item, index) =>
|
||||
renderSettingItem(item, index, group.items.length)
|
||||
)}
|
||||
{group.items.map((item, index) => renderSettingItem(item, index, group.items.length))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// 退出登录按钮
|
||||
const renderLogoutButton = () => (
|
||||
<TouchableOpacity
|
||||
style={styles.logoutButton}
|
||||
@@ -187,13 +324,11 @@ export const SettingsScreen: React.FC = () => {
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
// 渲染内容
|
||||
const renderContent = () => (
|
||||
<>
|
||||
{SETTINGS_GROUPS.map((group, index) => renderGroup(group, index))}
|
||||
{settingsGroups.map((group) => renderGroup(group))}
|
||||
{renderLogoutButton()}
|
||||
|
||||
{/* 底部版权信息 */}
|
||||
<View style={styles.footer}>
|
||||
<Text variant="caption" color={colors.text.hint}>
|
||||
萝卜社区 v{APP_VERSION}
|
||||
@@ -222,98 +357,4 @@ export const SettingsScreen: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingVertical: spacing.md,
|
||||
},
|
||||
groupContainer: {
|
||||
marginBottom: spacing.lg,
|
||||
},
|
||||
groupHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: spacing.lg,
|
||||
marginBottom: spacing.sm,
|
||||
gap: spacing.xs,
|
||||
},
|
||||
groupTitle: {
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.sm,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: colors.background.paper,
|
||||
marginHorizontal: spacing.lg,
|
||||
borderRadius: borderRadius.lg,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
settingItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: spacing.md,
|
||||
paddingHorizontal: spacing.md,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.divider,
|
||||
},
|
||||
settingItemFirst: {
|
||||
borderTopLeftRadius: borderRadius.lg,
|
||||
borderTopRightRadius: borderRadius.lg,
|
||||
},
|
||||
settingItemLast: {
|
||||
borderBottomLeftRadius: borderRadius.lg,
|
||||
borderBottomRightRadius: borderRadius.lg,
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
settingItemLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: borderRadius.md,
|
||||
backgroundColor: colors.primary.light + '20',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
dangerIconContainer: {
|
||||
backgroundColor: colors.error.light + '20',
|
||||
},
|
||||
settingContent: {
|
||||
flex: 1,
|
||||
},
|
||||
subtitle: {
|
||||
marginTop: 2,
|
||||
},
|
||||
logoutButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.background.paper,
|
||||
marginHorizontal: spacing.lg,
|
||||
marginTop: spacing.sm,
|
||||
marginBottom: spacing.lg,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: borderRadius.lg,
|
||||
gap: spacing.sm,
|
||||
},
|
||||
logoutText: {
|
||||
fontWeight: '500',
|
||||
},
|
||||
footer: {
|
||||
alignItems: 'center',
|
||||
marginTop: spacing.xl,
|
||||
paddingBottom: spacing.xl,
|
||||
},
|
||||
copyright: {
|
||||
marginTop: spacing.xs,
|
||||
},
|
||||
});
|
||||
|
||||
export default SettingsScreen;
|
||||
|
||||
Reference in New Issue
Block a user