refactor(stores): reorganize flat store files into modular directory structure
Migrate from flat file organization to modular directory structure under src/stores/: - auth/: authStore, registerStore, verificationStore, sessionStore - call/: callStore - settings/: chatSettingsStore, themeStore - ui/: homeTabBarVisibilityStore, homeTabPressStore - utils/: routePayloadCache - group/sources.ts, group/profileResolver.ts - message/sources.ts - post/sources.ts Update all import paths across components and screens to use new module paths. Maintain backward compatibility through deprecated re-export files for gradual migration.
This commit is contained in:
340
src/stores/settings/chatSettingsStore.ts
Normal file
340
src/stores/settings/chatSettingsStore.ts
Normal file
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* 聊天设置 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: 'white',
|
||||
name: '纯白',
|
||||
primary: '#FF6B35',
|
||||
secondary: '#F0F2F5',
|
||||
bubble: '#E8E8E8',
|
||||
icon: '🤍',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#F5F5F5',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'green',
|
||||
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',
|
||||
},
|
||||
{
|
||||
id: 'coral',
|
||||
name: '珊瑚橙',
|
||||
primary: '#FF6B6B',
|
||||
secondary: '#FFF0F0',
|
||||
bubble: '#FFD4D4',
|
||||
icon: '🐠',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FFF5F5',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'rose',
|
||||
name: '玫瑰粉',
|
||||
primary: '#E91E63',
|
||||
secondary: '#FCE4EC',
|
||||
bubble: '#F8BBD9',
|
||||
icon: '🌹',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FCE4EC',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'indigo',
|
||||
name: '靛蓝紫',
|
||||
primary: '#3F51B5',
|
||||
secondary: '#E8EAF6',
|
||||
bubble: '#C5CAE9',
|
||||
icon: '🐉',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E8EAF6',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'mint',
|
||||
name: '薄荷绿',
|
||||
primary: '#00BCD4',
|
||||
secondary: '#E0F7FA',
|
||||
bubble: '#B2EBF2',
|
||||
icon: '🧊',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E0F7FA',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'cherry',
|
||||
name: '樱桃红',
|
||||
primary: '#D32F2F',
|
||||
secondary: '#FFEBEE',
|
||||
bubble: '#FFCDD2',
|
||||
icon: '🍒',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FFEBEE',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'lavender',
|
||||
name: '薰衣草',
|
||||
primary: '#7E57C2',
|
||||
secondary: '#EDE7F6',
|
||||
bubble: '#D1C4E9',
|
||||
icon: '💜',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#EDE7F6',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'sunset',
|
||||
name: '落日橙',
|
||||
primary: '#FF5722',
|
||||
secondary: '#FBE9E7',
|
||||
bubble: '#FFCCBC',
|
||||
icon: '🌅',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FBE9E7',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'ocean',
|
||||
name: '深海蓝',
|
||||
primary: '#006064',
|
||||
secondary: '#B2EBF2',
|
||||
bubble: '#80DEEA',
|
||||
icon: '🌊',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E0F7FA',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'forest',
|
||||
name: '森林绿',
|
||||
primary: '#1B5E20',
|
||||
secondary: '#C8E6C9',
|
||||
bubble: '#A5D6A7',
|
||||
icon: '🌳',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E8F5E9',
|
||||
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,
|
||||
}))
|
||||
);
|
||||
}
|
||||
26
src/stores/settings/index.ts
Normal file
26
src/stores/settings/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 设置模块统一导出
|
||||
*/
|
||||
|
||||
export {
|
||||
useChatSettingsStore,
|
||||
useChatSettingsActions,
|
||||
useChatFontSize,
|
||||
useChatMessageRadius,
|
||||
useChatTheme,
|
||||
useChatNightMode,
|
||||
CHAT_THEMES,
|
||||
} from './chatSettingsStore';
|
||||
export type { ChatSettings, ChatThemeId } from './chatSettingsStore';
|
||||
|
||||
export {
|
||||
useAppColors,
|
||||
useThemePreference,
|
||||
useResolvedColorScheme,
|
||||
useSetThemePreference,
|
||||
usePaperThemeFromStore,
|
||||
ThemeBootstrap,
|
||||
useThemeStore,
|
||||
getSystemScheme,
|
||||
} from './themeStore';
|
||||
export type { ThemePreference, ResolvedScheme } from './themeStore';
|
||||
168
src/stores/settings/themeStore.ts
Normal file
168
src/stores/settings/themeStore.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { Appearance, AppState, AppStateStatus } from 'react-native';
|
||||
import { create } from 'zustand';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { lightColors, darkColors, type AppColors } from '../../theme/palettes';
|
||||
import { buildPaperTheme } from '../../theme/paperTheme';
|
||||
import type { MD3Theme } from 'react-native-paper';
|
||||
|
||||
const STORAGE_KEY = 'app_theme_preference';
|
||||
|
||||
export type ThemePreference = 'light' | 'dark' | 'system';
|
||||
|
||||
export type ResolvedScheme = 'light' | 'dark';
|
||||
|
||||
function computeResolved(pref: ThemePreference, system: ResolvedScheme): ResolvedScheme {
|
||||
return pref === 'system' ? system : pref;
|
||||
}
|
||||
|
||||
function buildState(
|
||||
preference: ThemePreference,
|
||||
systemScheme: ResolvedScheme
|
||||
): {
|
||||
resolvedScheme: ResolvedScheme;
|
||||
colors: AppColors;
|
||||
paperTheme: MD3Theme;
|
||||
} {
|
||||
const resolvedScheme = computeResolved(preference, systemScheme);
|
||||
const isDark = resolvedScheme === 'dark';
|
||||
const colors = isDark ? darkColors : lightColors;
|
||||
return {
|
||||
resolvedScheme,
|
||||
colors,
|
||||
paperTheme: buildPaperTheme(colors, isDark),
|
||||
};
|
||||
}
|
||||
|
||||
type ThemeState = {
|
||||
preference: ThemePreference;
|
||||
systemScheme: ResolvedScheme;
|
||||
hydrated: boolean;
|
||||
resolvedScheme: ResolvedScheme;
|
||||
colors: AppColors;
|
||||
paperTheme: MD3Theme;
|
||||
setPreference: (p: ThemePreference) => Promise<void>;
|
||||
setSystemScheme: (s: ResolvedScheme) => void;
|
||||
hydrate: () => Promise<void>;
|
||||
};
|
||||
|
||||
/** 获取系统主题 */
|
||||
function getSystemScheme(): ResolvedScheme {
|
||||
const scheme = Appearance.getColorScheme();
|
||||
return scheme === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
const initialSystemScheme = getSystemScheme();
|
||||
|
||||
export const useThemeStore = create<ThemeState>((set, get) => ({
|
||||
preference: 'system',
|
||||
systemScheme: initialSystemScheme,
|
||||
hydrated: false,
|
||||
...buildState('system', initialSystemScheme),
|
||||
|
||||
setSystemScheme: (systemScheme) => {
|
||||
const { preference, systemScheme: cur } = get();
|
||||
if (cur === systemScheme) return;
|
||||
set({
|
||||
systemScheme,
|
||||
...buildState(preference, systemScheme),
|
||||
});
|
||||
},
|
||||
|
||||
setPreference: async (preference) => {
|
||||
try {
|
||||
await AsyncStorage.setItem(STORAGE_KEY, preference);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const { systemScheme } = get();
|
||||
set({
|
||||
preference,
|
||||
...buildState(preference, systemScheme),
|
||||
});
|
||||
},
|
||||
|
||||
hydrate: async () => {
|
||||
let preference: ThemePreference = 'system';
|
||||
try {
|
||||
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
||||
if (raw === 'light' || raw === 'dark' || raw === 'system') {
|
||||
preference = raw;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const systemScheme = getSystemScheme();
|
||||
set({
|
||||
preference,
|
||||
hydrated: true,
|
||||
systemScheme,
|
||||
...buildState(preference, systemScheme),
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
/** 在根布局中同步系统配色并恢复持久化偏好 */
|
||||
export function ThemeBootstrap() {
|
||||
const setSystemScheme = useThemeStore((s) => s.setSystemScheme);
|
||||
const hydrate = useThemeStore((s) => s.hydrate);
|
||||
const appState = useRef<AppStateStatus>(AppState.currentState);
|
||||
|
||||
useEffect(() => {
|
||||
void hydrate();
|
||||
}, [hydrate]);
|
||||
|
||||
// 监听系统主题变化
|
||||
useEffect(() => {
|
||||
const subscription = Appearance.addChangeListener(({ colorScheme }) => {
|
||||
const newScheme = colorScheme === 'dark' ? 'dark' : 'light';
|
||||
setSystemScheme(newScheme);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, [setSystemScheme]);
|
||||
|
||||
// 监听应用状态变化,从后台恢复时重新检测系统主题
|
||||
useEffect(() => {
|
||||
const subscription = AppState.addEventListener('change', (nextAppState) => {
|
||||
if (
|
||||
appState.current.match(/inactive|background/) &&
|
||||
nextAppState === 'active'
|
||||
) {
|
||||
setSystemScheme(getSystemScheme());
|
||||
}
|
||||
appState.current = nextAppState;
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, [setSystemScheme]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useAppColors() {
|
||||
return useThemeStore((s) => s.colors);
|
||||
}
|
||||
|
||||
export function useThemePreference() {
|
||||
return useThemeStore((s) => s.preference);
|
||||
}
|
||||
|
||||
export function useResolvedColorScheme() {
|
||||
return useThemeStore((s) => s.resolvedScheme);
|
||||
}
|
||||
|
||||
export function useSetThemePreference() {
|
||||
return useThemeStore((s) => s.setPreference);
|
||||
}
|
||||
|
||||
export function usePaperThemeFromStore() {
|
||||
return useThemeStore((s) => s.paperTheme);
|
||||
}
|
||||
|
||||
// 导出调试函数
|
||||
export { getSystemScheme };
|
||||
Reference in New Issue
Block a user