feat(ui): unify design system to Twitter/X style across screens and improve message handling
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 3m9s
Frontend CI / ota-android (push) Successful in 10m29s
Frontend CI / build-android-apk (push) Successful in 43m4s

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:
lafay
2026-04-25 00:39:40 +08:00
parent e0d28535f4
commit ad19bc2af7
26 changed files with 950 additions and 1121 deletions

View File

@@ -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}