feat(chat): enhance chat settings and message bubble styles
All checks were successful
Frontend CI / ota-android (push) Successful in 12m37s
Frontend CI / build-and-push-web (push) Successful in 22m42s
Frontend CI / build-android-apk (push) Successful in 57m42s

- Introduced dynamic chat settings for font size and message bubble radius, improving user customization.
- Updated ChatScreen styles to support dynamic themes and responsive layouts.
- Refactored bubble styles to utilize dynamic properties for better visual consistency.
- Simplified chat settings management by integrating Zustand store for state management.

This update significantly enhances the chat interface and user experience by allowing personalized settings and improved visual elements.
This commit is contained in:
lafay
2026-04-01 16:30:44 +08:00
parent c771bd9755
commit 72842352d9
14 changed files with 507 additions and 109 deletions

View File

@@ -16,6 +16,7 @@ import { User } from '../types';
import { authService, resolveAuthApiError, LoginRequest, RegisterRequest } from '../services';
import { wsService } from '../services/wsService';
import { callStore } from './callStore';
import { useSessionStore } from './sessionStore';
import {
initDatabase,
closeDatabase,
@@ -149,6 +150,9 @@ export const useAuthStore = create<AuthState>((set) => ({
// 4. 写用户缓存DB 已就绪)
await cacheUser(user);
// 5. 同步 userId 到 sessionStore
useSessionStore.getState().setUserId(userId);
set({
isAuthenticated: true,
currentUser: user,
@@ -156,7 +160,7 @@ export const useAuthStore = create<AuthState>((set) => ({
error: null,
});
// 5. 启动 SSE
// 6. 启动 SSE
await startRealtime();
return true;
@@ -187,6 +191,9 @@ export const useAuthStore = create<AuthState>((set) => ({
await saveUserId(userId);
await cacheUser(user);
// 同步 userId 到 sessionStore
useSessionStore.getState().setUserId(userId);
set({
isAuthenticated: true,
currentUser: user,
@@ -223,7 +230,9 @@ export const useAuthStore = create<AuthState>((set) => ({
await closeDatabase();
// 6. 清除持久化的 userId
await clearUserId();
// 7. 清除 userManager 的内存缓存
// 7. 清除 sessionStore 的 userId
useSessionStore.getState().setUserId(null);
// 8. 清除 userManager 的内存缓存
const { userManager } = await import('./userManager');
userManager.invalidateUsers();
} catch (error) {
@@ -272,17 +281,21 @@ export const useAuthStore = create<AuthState>((set) => ({
// 5. DB 已就绪,更新用户缓存
await cacheUser(user);
// 6. 同步 userId 到 sessionStore
useSessionStore.getState().setUserId(userId);
set({
isAuthenticated: true,
currentUser: user,
isLoading: false,
});
// 6. 启动 SSE
// 7. 启动 SSE
await startRealtime();
} else {
// Token 已失效或不存在
await clearUserId();
useSessionStore.getState().setUserId(null);
set({
isAuthenticated: false,
currentUser: null,

View File

@@ -8,7 +8,7 @@ import {
WSErrorMessage,
} from '../services/wsService';
import { webrtcManager, ICEServer } from '../services/webrtc';
import { useAuthStore } from './authStore';
import { getCurrentUserId } from './sessionStore';
import { useUserStore } from './userStore';
import { userManager } from './userManager';
@@ -444,7 +444,7 @@ export const callStore = create<CallState>((set, get) => ({
callTimeoutTimer = null;
}
const myUserId = useAuthStore.getState().currentUser?.id || '';
const myUserId = getCurrentUserId() || '';
try {
const isVideoCall = currentCall.callType === 'video';
@@ -555,7 +555,7 @@ export const callStore = create<CallState>((set, get) => ({
const { currentCall } = get();
if (!currentCall || currentCall.id !== msg.call_id) return;
const myUserId = useAuthStore.getState().currentUser?.id || '';
const myUserId = getCurrentUserId() || '';
if (msg.from_id === myUserId) return;
if (msg.payload.sdp_type === 'offer') {
@@ -571,7 +571,7 @@ export const callStore = create<CallState>((set, get) => ({
const { currentCall } = get();
if (!currentCall || currentCall.id !== msg.call_id) return;
const myUserId = useAuthStore.getState().currentUser?.id || '';
const myUserId = getCurrentUserId() || '';
if (msg.from_id === myUserId) return;
try {
@@ -616,7 +616,7 @@ export const callStore = create<CallState>((set, get) => ({
return;
}
const myUserId = useAuthStore.getState().currentUser?.id;
const myUserId = getCurrentUserId();
if (!myUserId) {
console.error('[CallStore] Not logged in');
return;
@@ -681,7 +681,7 @@ export const callStore = create<CallState>((set, get) => ({
const isVideoCall = incomingCall.callType === 'video';
console.log('[CallStore] acceptCall, isVideoCall:', isVideoCall);
const myUserId = useAuthStore.getState().currentUser?.id || '';
const myUserId = getCurrentUserId() || '';
set({
currentCall: {

View File

@@ -0,0 +1,201 @@
/**
* 聊天设置 Store
* 持久化存储用户的聊天样式偏好设置
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { useShallow } from 'zustand/react/shallow';
// 主题配置 - 扩展包含更多颜色
export const CHAT_THEMES = [
{
id: 'default',
name: '默认绿',
primary: '#4CAF50',
secondary: '#E8F5E9',
bubble: '#DCF8C6',
icon: '🏠',
// 扩展颜色
card: '#FFFFFF', // 卡片背景
inputBg: '#FFFFFF', // 输入框背景
panelBg: '#F5F5F5', // 面板背景
headerBg: '#FFFFFF', // 头部背景
textPrimary: '#1A1A1A', // 主要文字
textSecondary: '#666666', // 次要文字
},
{
id: 'yellow',
name: '活力黄',
primary: '#FFC107',
secondary: '#FFF8E1',
bubble: '#FFECB3',
icon: '🐥',
card: '#FFFFFF',
inputBg: '#FFFFFF',
panelBg: '#FFFDE7',
headerBg: '#FFFFFF',
textPrimary: '#1A1A1A',
textSecondary: '#666666',
},
{
id: 'blue',
name: '清新蓝',
primary: '#2196F3',
secondary: '#E3F2FD',
bubble: '#BBDEFB',
icon: '☃️',
card: '#FFFFFF',
inputBg: '#FFFFFF',
panelBg: '#E1F5FE',
headerBg: '#FFFFFF',
textPrimary: '#1A1A1A',
textSecondary: '#666666',
},
{
id: 'purple',
name: '梦幻紫',
primary: '#9C27B0',
secondary: '#F3E5F5',
bubble: '#E1BEE7',
icon: '💎',
card: '#FFFFFF',
inputBg: '#FFFFFF',
panelBg: '#F3E5F5',
headerBg: '#FFFFFF',
textPrimary: '#1A1A1A',
textSecondary: '#666666',
},
{
id: 'teal',
name: '自然青',
primary: '#009688',
secondary: '#E0F2F1',
bubble: '#B2DFDB',
icon: '🦁',
card: '#FFFFFF',
inputBg: '#FFFFFF',
panelBg: '#E0F2F1',
headerBg: '#FFFFFF',
textPrimary: '#1A1A1A',
textSecondary: '#666666',
},
] as const;
export type ChatThemeId = typeof CHAT_THEMES[number]['id'];
export interface ChatSettings {
// 消息字号 (12-24)
fontSize: number;
// 消息圆角 (0-30)
messageRadius: number;
// 选中的主题索引
themeIndex: number;
// 夜间模式
nightMode: boolean;
// 聊天壁纸 (可选)
wallpaper?: string | null;
// 名称颜色 (可选)
nameColor?: string | null;
}
const DEFAULT_SETTINGS: ChatSettings = {
fontSize: 16,
messageRadius: 12,
themeIndex: 0,
nightMode: false,
wallpaper: null,
nameColor: null,
};
interface ChatSettingsState extends ChatSettings {
// 获取当前主题
getCurrentTheme: () => typeof CHAT_THEMES[number];
// 更新设置
updateSettings: (settings: Partial<ChatSettings>) => void;
// 重置为默认
resetSettings: () => void;
// 设置字号
setFontSize: (size: number) => void;
// 设置圆角
setMessageRadius: (radius: number) => void;
// 设置主题
setTheme: (index: number) => void;
// 切换夜间模式
toggleNightMode: () => void;
}
const STORAGE_KEY = 'chat_settings';
export const useChatSettingsStore = create<ChatSettingsState>()(
persist(
(set, get) => ({
...DEFAULT_SETTINGS,
getCurrentTheme: () => {
const { themeIndex } = get();
return CHAT_THEMES[themeIndex] ?? CHAT_THEMES[0];
},
updateSettings: (settings) => {
set((state) => ({ ...state, ...settings }));
},
resetSettings: () => {
set(DEFAULT_SETTINGS);
},
setFontSize: (fontSize) => {
set({ fontSize: Math.max(12, Math.min(24, fontSize)) });
},
setMessageRadius: (messageRadius) => {
set({ messageRadius: Math.max(0, Math.min(30, messageRadius)) });
},
setTheme: (themeIndex) => {
set({ themeIndex: Math.max(0, Math.min(CHAT_THEMES.length - 1, themeIndex)) });
},
toggleNightMode: () => {
set((state) => ({ nightMode: !state.nightMode }));
},
}),
{
name: STORAGE_KEY,
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// 导出便捷 hooks
export function useChatFontSize() {
return useChatSettingsStore((s) => s.fontSize);
}
export function useChatMessageRadius() {
return useChatSettingsStore((s) => s.messageRadius);
}
export function useChatTheme() {
const themeIndex = useChatSettingsStore((s) => s.themeIndex);
return CHAT_THEMES[themeIndex];
}
export function useChatNightMode() {
return useChatSettingsStore((s) => s.nightMode);
}
export function useChatSettingsActions() {
return useChatSettingsStore(
useShallow((s) => ({
setFontSize: s.setFontSize,
setMessageRadius: s.setMessageRadius,
setTheme: s.setTheme,
toggleNightMode: s.toggleNightMode,
updateSettings: s.updateSettings,
resetSettings: s.resetSettings,
}))
);
}

View File

@@ -3,6 +3,19 @@
*/
export { useAuthStore, useCurrentUser, useIsAuthenticated, useAuthLoading } from './authStore';
// 聊天设置 Store
export {
useChatSettingsStore,
useChatSettingsActions,
useChatFontSize,
useChatMessageRadius,
useChatTheme,
useChatNightMode,
CHAT_THEMES,
} from './chatSettingsStore';
export type { ChatSettings, ChatThemeId } from './chatSettingsStore';
export {
useUserStore,
usePosts,

View File

@@ -0,0 +1,27 @@
/**
* 会话状态管理
*
* 这是一个轻量级的 store只存储当前用户的身份信息userId
* 它的目的是打破 authStore 和 callStore 之间的循环依赖:
*
* sessionStore (存储 userId)
* ↑ ↑
* authStore callStore
*
* authStore 在登录成功后设置 userIdcallStore 从 sessionStore 读取 userId。
*/
import { create } from 'zustand';
interface SessionState {
userId: string | null;
setUserId: (userId: string | null) => void;
}
export const useSessionStore = create<SessionState>((set) => ({
userId: null,
setUserId: (userId) => set({ userId }),
}));
// 导出便捷函数,用于非 React 组件中直接获取 userId
export const getCurrentUserId = () => useSessionStore.getState().userId;