feat(ui): unify design system to Twitter/X style across screens and improve message handling
Redesign multiple screens and components from card-based responsive design to Twitter/X flat design: - UserProfileHeader: adopt Twitter/X style with larger icons, simplified buttons, and block functionality - AppBackButton: update to chevron-left icon with larger size, remove background styling - Update SettingsScreen, EditProfileScreen, and UserProfileScreen with flat layout approach - ConversationListRow: convert from bordered cards to flat list rows with updated typography Improve message system with WebSocket notification handling: - Add real-time system unread count updates via WebSocket - Track lastSystemMessageAt for accurate system message timestamps - Add notification deduplication and read-status filtering - Combine system and conversation unread counts in background sync - Clear system notifications from notification center on mark as read
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 设置页 SettingsScreen(响应式适配)
|
||||
* 设置页 SettingsScreen - Twitter/X 风格
|
||||
* 威友 - 应用设置
|
||||
* 在宽屏下居中显示,最大宽度限制
|
||||
* 采用扁平化设计,全宽布局,简洁分割线
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
@@ -31,8 +31,7 @@ import { useResponsive, useResponsiveSpacing } from '../../hooks';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
// 设置卡片最大宽度
|
||||
const SETTINGS_CARD_MAX_WIDTH = 720;
|
||||
const APP_VERSION = Constants.expoConfig?.version || '1.0.0';
|
||||
|
||||
interface SettingsItem {
|
||||
key: string;
|
||||
@@ -44,12 +43,9 @@ interface SettingsItem {
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
const APP_VERSION = Constants.expoConfig?.version || '1.0.0';
|
||||
|
||||
const SETTINGS_GROUPS_BASE = [
|
||||
{
|
||||
title: '账号与安全',
|
||||
icon: 'shield-check-outline',
|
||||
items: [
|
||||
{ key: 'edit_profile', title: '编辑资料', icon: 'account-edit-outline', showArrow: true },
|
||||
{ key: 'verification', title: '身份认证', icon: 'check-decagram', showArrow: true, subtitle: '获得认证标识,解锁更多功能' },
|
||||
@@ -61,7 +57,6 @@ const SETTINGS_GROUPS_BASE = [
|
||||
},
|
||||
{
|
||||
title: '通知与通用',
|
||||
icon: 'bell-outline',
|
||||
items: [
|
||||
{ key: 'notification_settings', title: '通知设置', icon: 'bell-cog-outline', showArrow: true, subtitle: '推送、震动、提示音' },
|
||||
{ key: 'data_storage', title: '数据与存储', icon: 'database-outline', showArrow: true },
|
||||
@@ -69,7 +64,6 @@ const SETTINGS_GROUPS_BASE = [
|
||||
},
|
||||
{
|
||||
title: '关于与帮助',
|
||||
icon: 'information-outline',
|
||||
items: [
|
||||
{ key: 'about', title: '关于我们', icon: 'information-outline', showArrow: true, subtitle: `版本 ${APP_VERSION}` },
|
||||
{ key: 'help', title: '帮助与反馈', icon: 'help-circle-outline', showArrow: true },
|
||||
@@ -81,39 +75,38 @@ function createSettingsStyles(colors: AppColors) {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.background.paper,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
scrollContent: {
|
||||
paddingVertical: spacing.lg,
|
||||
paddingVertical: spacing.sm,
|
||||
},
|
||||
groupContainer: {
|
||||
marginBottom: spacing['2xl'],
|
||||
maxWidth: SETTINGS_CARD_MAX_WIDTH,
|
||||
alignSelf: 'center',
|
||||
width: '100%',
|
||||
marginBottom: spacing.xl,
|
||||
backgroundColor: colors.background.paper,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.divider,
|
||||
},
|
||||
groupHeader: {
|
||||
paddingHorizontal: spacing['2xl'],
|
||||
marginBottom: spacing.sm,
|
||||
marginTop: spacing.sm,
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingVertical: spacing.sm,
|
||||
backgroundColor: colors.background.default,
|
||||
},
|
||||
groupTitle: {
|
||||
fontWeight: '600',
|
||||
fontSize: fontSizes.sm,
|
||||
color: colors.text.secondary,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
settingItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: spacing['2xl'],
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: spacing.lg,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.divider,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
settingItemFirst: {},
|
||||
settingItemLast: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
@@ -123,13 +116,17 @@ function createSettingsStyles(colors: AppColors) {
|
||||
flex: 1,
|
||||
},
|
||||
iconContainer: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: borderRadius.md,
|
||||
backgroundColor: colors.background.default,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: spacing.lg,
|
||||
marginRight: spacing.md,
|
||||
},
|
||||
dangerIconContainer: {
|
||||
backgroundColor: colors.error.light + '20',
|
||||
},
|
||||
dangerIconContainer: {},
|
||||
settingContent: {
|
||||
flex: 1,
|
||||
},
|
||||
@@ -141,22 +138,21 @@ function createSettingsStyles(colors: AppColors) {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: spacing.lg,
|
||||
marginBottom: spacing['2xl'],
|
||||
paddingVertical: 16,
|
||||
marginHorizontal: spacing['2xl'],
|
||||
borderRadius: 14,
|
||||
marginBottom: spacing.xl,
|
||||
paddingVertical: 14,
|
||||
marginHorizontal: spacing.lg,
|
||||
borderRadius: borderRadius.full,
|
||||
gap: spacing.sm,
|
||||
maxWidth: SETTINGS_CARD_MAX_WIDTH,
|
||||
alignSelf: 'center',
|
||||
width: '100%',
|
||||
backgroundColor: colors.error.light + '20',
|
||||
backgroundColor: colors.background.paper,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.divider,
|
||||
},
|
||||
logoutText: {
|
||||
fontWeight: '600',
|
||||
fontWeight: '700',
|
||||
},
|
||||
footer: {
|
||||
alignItems: 'center',
|
||||
marginTop: spacing.xl,
|
||||
marginTop: spacing.md,
|
||||
paddingBottom: spacing.xl,
|
||||
},
|
||||
copyright: {
|
||||
@@ -170,7 +166,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
const { logout } = useAuthStore();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { isMobile } = useResponsive();
|
||||
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 colors = useAppColors();
|
||||
const styles = useMemo(() => createSettingsStyles(colors), [colors]);
|
||||
const themePreference = useThemePreference();
|
||||
@@ -184,7 +180,6 @@ export const SettingsScreen: React.FC = () => {
|
||||
return [
|
||||
{
|
||||
title: '个性化',
|
||||
icon: 'palette-outline',
|
||||
items: [
|
||||
{
|
||||
key: 'chat_settings',
|
||||
@@ -307,16 +302,16 @@ export const SettingsScreen: React.FC = () => {
|
||||
<View style={[styles.iconContainer, item.danger && styles.dangerIconContainer]}>
|
||||
<MaterialCommunityIcons
|
||||
name={item.icon as any}
|
||||
size={22}
|
||||
size={20}
|
||||
color={item.danger ? colors.error.main : colors.text.secondary}
|
||||
/>
|
||||
</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} style={{ fontWeight: '500' }}>
|
||||
{item.title}
|
||||
</Text>
|
||||
{item.subtitle && (
|
||||
<Text variant="caption" color={colors.text.secondary} style={styles.subtitle}>
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.subtitle}>
|
||||
{item.subtitle}
|
||||
</Text>
|
||||
)}
|
||||
@@ -329,13 +324,13 @@ export const SettingsScreen: React.FC = () => {
|
||||
);
|
||||
|
||||
const renderGroup = (group: (typeof settingsGroups)[0]) => (
|
||||
<View key={group.title} style={styles.groupContainer}>
|
||||
<View key={group.title}>
|
||||
<View style={styles.groupHeader}>
|
||||
<Text variant="caption" style={styles.groupTitle}>
|
||||
{group.title}
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<View style={styles.groupContainer}>
|
||||
{group.items.map((item, index) => renderSettingItem(item, index, group.items.length))}
|
||||
</View>
|
||||
</View>
|
||||
@@ -345,9 +340,9 @@ export const SettingsScreen: React.FC = () => {
|
||||
<TouchableOpacity
|
||||
style={styles.logoutButton}
|
||||
onPress={() => handleItemPress('logout')}
|
||||
activeOpacity={0.7}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<MaterialCommunityIcons name="logout-variant" size={20} color={colors.error.main} />
|
||||
<MaterialCommunityIcons name="logout-variant" size={18} color={colors.error.main} />
|
||||
<Text variant="body" color={colors.error.main} style={styles.logoutText}>
|
||||
退出登录
|
||||
</Text>
|
||||
@@ -356,11 +351,8 @@ export const SettingsScreen: React.FC = () => {
|
||||
|
||||
const renderContent = () => (
|
||||
<>
|
||||
{/* 设置分组 */}
|
||||
{settingsGroups.map((group) => renderGroup(group))}
|
||||
|
||||
{renderLogoutButton()}
|
||||
|
||||
<View style={styles.footer}>
|
||||
<Text variant="caption" color={colors.text.hint}>
|
||||
威友 v{APP_VERSION}
|
||||
|
||||
Reference in New Issue
Block a user