feat(chat): enhance chat settings and message bubble styles
- 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:
@@ -132,10 +132,10 @@ function createImageGridStyles(colors: AppColors) {
|
||||
aspectRatio: 1,
|
||||
},
|
||||
gridItem2: {
|
||||
width: '49%',
|
||||
width: '48.5%',
|
||||
},
|
||||
gridItem3: {
|
||||
width: '32.5%',
|
||||
width: '31.8%',
|
||||
},
|
||||
masonryContainer: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface Step {
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface StepIndicatorProps {
|
||||
export interface StepIndicatorProps {
|
||||
steps: Step[];
|
||||
currentStep: number;
|
||||
containerStyle?: ViewStyle;
|
||||
|
||||
@@ -201,7 +201,7 @@ export const VerificationCodeInput: React.FC<VerificationCodeInputProps> = ({
|
||||
};
|
||||
|
||||
function createStyles(colors: AppColors) {
|
||||
return {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -225,7 +225,7 @@ function createStyles(colors: AppColors) {
|
||||
},
|
||||
cellFocused: {
|
||||
borderColor: colors.primary.main,
|
||||
backgroundColor: `${colors.primary.main}10`, // 10% 透明度的主色
|
||||
backgroundColor: `${colors.primary.main}10`,
|
||||
},
|
||||
cellFilled: {
|
||||
borderColor: colors.primary.main,
|
||||
@@ -250,7 +250,7 @@ function createStyles(colors: AppColors) {
|
||||
backgroundColor: colors.primary.main,
|
||||
borderRadius: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default VerificationCodeInput;
|
||||
|
||||
@@ -262,7 +262,6 @@ export const RegisterScreen: React.FC = () => {
|
||||
{/* 步骤指示器 */}
|
||||
<RegisterStepIndicator
|
||||
currentStep={currentStep}
|
||||
completedSteps={completedSteps}
|
||||
containerStyle={styles.stepIndicator}
|
||||
/>
|
||||
|
||||
|
||||
@@ -105,16 +105,16 @@ export const RegisterStep1Email: React.FC<RegisterStepProps> = ({
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.actionButton,
|
||||
(sendingCode || countdown > 0) && styles.actionButtonDisabled,
|
||||
(sendingCode || (countdown ?? 0) > 0) && styles.actionButtonDisabled,
|
||||
]}
|
||||
onPress={handleSendCode}
|
||||
disabled={sendingCode || countdown > 0}
|
||||
disabled={sendingCode || (countdown ?? 0) > 0}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<Text style={styles.actionButtonText}>
|
||||
{sendingCode
|
||||
? '发送中...'
|
||||
: countdown > 0
|
||||
: (countdown ?? 0) > 0
|
||||
? `${countdown}秒后重试`
|
||||
: '获取验证码'}
|
||||
</Text>
|
||||
|
||||
@@ -5,13 +5,20 @@
|
||||
|
||||
import { StyleSheet, ViewStyle, TextStyle } from 'react-native';
|
||||
import { spacing, type AppColors } from '../../../../theme';
|
||||
import { useChatSettingsStore } from '../../../../stores/chatSettingsStore';
|
||||
|
||||
// 默认圆角,但会从 store 读取
|
||||
export const BUBBLE_RADIUS = 16;
|
||||
|
||||
export function getBubbleColors(colors: AppColors) {
|
||||
// 获取动态圆角值
|
||||
export function useBubbleRadius(): number {
|
||||
return useChatSettingsStore((s) => s.messageRadius);
|
||||
}
|
||||
|
||||
export function getBubbleColors(colors: AppColors, outgoingBubbleColor?: string) {
|
||||
return {
|
||||
outgoing: {
|
||||
background: colors.chat.bubbleOutgoing,
|
||||
background: outgoingBubbleColor || colors.chat.bubbleOutgoing,
|
||||
text: colors.chat.textPrimary,
|
||||
},
|
||||
incoming: {
|
||||
@@ -27,6 +34,76 @@ export function getBubbleColors(colors: AppColors) {
|
||||
|
||||
export type MessageGroupPosition = 'single' | 'first' | 'middle' | 'last';
|
||||
|
||||
// 使用动态圆角的 hook
|
||||
export function useBubbleBorderRadius(isMe: boolean, position: MessageGroupPosition): ViewStyle {
|
||||
const radius = useBubbleRadius();
|
||||
|
||||
if (isMe) {
|
||||
switch (position) {
|
||||
case 'single':
|
||||
return {
|
||||
borderTopLeftRadius: radius,
|
||||
borderTopRightRadius: radius,
|
||||
borderBottomLeftRadius: radius,
|
||||
borderBottomRightRadius: 4,
|
||||
};
|
||||
case 'first':
|
||||
return {
|
||||
borderTopLeftRadius: radius,
|
||||
borderTopRightRadius: radius,
|
||||
borderBottomLeftRadius: radius,
|
||||
borderBottomRightRadius: 4,
|
||||
};
|
||||
case 'middle':
|
||||
return {
|
||||
borderTopLeftRadius: radius,
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomLeftRadius: radius,
|
||||
borderBottomRightRadius: 4,
|
||||
};
|
||||
case 'last':
|
||||
return {
|
||||
borderTopLeftRadius: radius,
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomLeftRadius: radius,
|
||||
borderBottomRightRadius: radius,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
switch (position) {
|
||||
case 'single':
|
||||
return {
|
||||
borderTopLeftRadius: 4,
|
||||
borderTopRightRadius: radius,
|
||||
borderBottomLeftRadius: radius,
|
||||
borderBottomRightRadius: radius,
|
||||
};
|
||||
case 'first':
|
||||
return {
|
||||
borderTopLeftRadius: 4,
|
||||
borderTopRightRadius: radius,
|
||||
borderBottomLeftRadius: 4,
|
||||
borderBottomRightRadius: radius,
|
||||
};
|
||||
case 'middle':
|
||||
return {
|
||||
borderTopLeftRadius: 4,
|
||||
borderTopRightRadius: radius,
|
||||
borderBottomLeftRadius: 4,
|
||||
borderBottomRightRadius: radius,
|
||||
};
|
||||
case 'last':
|
||||
return {
|
||||
borderTopLeftRadius: radius,
|
||||
borderTopRightRadius: radius,
|
||||
borderBottomLeftRadius: radius,
|
||||
borderBottomRightRadius: radius,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保持原有的静态函数用于兼容
|
||||
export const getBubbleBorderRadius = (isMe: boolean, position: MessageGroupPosition): ViewStyle => {
|
||||
const radius = BUBBLE_RADIUS;
|
||||
|
||||
@@ -132,8 +209,8 @@ export const shouldShowSenderInfo = (
|
||||
return prevMessage.sender_id !== currentMessage.sender_id;
|
||||
};
|
||||
|
||||
export const getBubbleBaseStyle = (isMe: boolean, colors: AppColors): ViewStyle => {
|
||||
const bc = getBubbleColors(colors);
|
||||
export const getBubbleBaseStyle = (isMe: boolean, colors: AppColors, outgoingBubbleColor?: string): ViewStyle => {
|
||||
const bc = getBubbleColors(colors, outgoingBubbleColor);
|
||||
return {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm + 4,
|
||||
@@ -148,19 +225,26 @@ export const getBubbleBaseStyle = (isMe: boolean, colors: AppColors): ViewStyle
|
||||
};
|
||||
};
|
||||
|
||||
export const getBubbleTextStyle = (isMe: boolean, colors: AppColors): TextStyle => {
|
||||
// 使用动态字号的 hook
|
||||
export function useChatFontSize(): number {
|
||||
return useChatSettingsStore((s) => s.fontSize);
|
||||
}
|
||||
|
||||
export const getBubbleTextStyle = (isMe: boolean, colors: AppColors, customFontSize?: number): TextStyle => {
|
||||
const bc = getBubbleColors(colors);
|
||||
const fontSize = customFontSize ?? 16;
|
||||
return {
|
||||
color: isMe ? bc.outgoing.text : bc.incoming.text,
|
||||
fontSize: 16,
|
||||
lineHeight: 23,
|
||||
fontSize,
|
||||
lineHeight: Math.round(fontSize * 1.4),
|
||||
letterSpacing: 0.2,
|
||||
fontWeight: '400',
|
||||
};
|
||||
};
|
||||
|
||||
export function createBubbleStyles(colors: AppColors) {
|
||||
const bc = getBubbleColors(colors);
|
||||
export function createBubbleStyles(colors: AppColors, customFontSize?: number, outgoingBubbleColor?: string) {
|
||||
const bc = getBubbleColors(colors, outgoingBubbleColor);
|
||||
const fontSize = customFontSize ?? 16;
|
||||
return StyleSheet.create({
|
||||
bubble: {
|
||||
paddingHorizontal: spacing.md,
|
||||
@@ -174,8 +258,8 @@ export function createBubbleStyles(colors: AppColors) {
|
||||
backgroundColor: bc.incoming.background,
|
||||
},
|
||||
text: {
|
||||
fontSize: 16,
|
||||
lineHeight: 23,
|
||||
fontSize,
|
||||
lineHeight: Math.round(fontSize * 1.4),
|
||||
letterSpacing: 0.2,
|
||||
fontWeight: '400',
|
||||
},
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* ChatScreen 样式定义
|
||||
* 支持响应式布局
|
||||
* 支持响应式布局和动态聊天设置
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { StyleSheet, Dimensions } from 'react-native';
|
||||
import { spacing, shadows, useAppColors, type AppColors } from '../../../../theme';
|
||||
import { useChatSettingsStore, CHAT_THEMES } from '../../../../stores/chatSettingsStore';
|
||||
|
||||
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
||||
|
||||
@@ -19,17 +20,66 @@ const BREAKPOINTS = {
|
||||
const isWideScreen = SCREEN_WIDTH >= BREAKPOINTS.desktop;
|
||||
const isTablet = SCREEN_WIDTH >= BREAKPOINTS.tablet;
|
||||
|
||||
export function createChatScreenStyles(colors: AppColors) {
|
||||
// Hook 获取动态样式参数
|
||||
export function useChatDynamicStyles() {
|
||||
const fontSize = useChatSettingsStore((s) => s.fontSize);
|
||||
const messageRadius = useChatSettingsStore((s) => s.messageRadius);
|
||||
const themeIndex = useChatSettingsStore((s) => s.themeIndex);
|
||||
const theme = CHAT_THEMES[themeIndex];
|
||||
|
||||
return {
|
||||
fontSize,
|
||||
messageRadius,
|
||||
// 气泡颜色
|
||||
outgoingBubbleColor: theme.bubble,
|
||||
// 背景色
|
||||
chatBackgroundColor: theme.secondary,
|
||||
// 卡片/头部背景色
|
||||
cardBackgroundColor: theme.card,
|
||||
// 输入框背景色
|
||||
inputBackgroundColor: theme.inputBg,
|
||||
// 面板背景色
|
||||
panelBackgroundColor: theme.panelBg,
|
||||
// 主要文字颜色
|
||||
textPrimaryColor: theme.textPrimary,
|
||||
// 次要文字颜色
|
||||
textSecondaryColor: theme.textSecondary,
|
||||
};
|
||||
}
|
||||
|
||||
export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
fontSize?: number;
|
||||
messageRadius?: number;
|
||||
outgoingBubbleColor?: string;
|
||||
chatBackgroundColor?: string;
|
||||
cardBackgroundColor?: string;
|
||||
inputBackgroundColor?: string;
|
||||
panelBackgroundColor?: string;
|
||||
textPrimaryColor?: string;
|
||||
textSecondaryColor?: string;
|
||||
}) {
|
||||
const fontSize = dynamicStyles?.fontSize ?? 16;
|
||||
const messageRadius = dynamicStyles?.messageRadius ?? 12;
|
||||
|
||||
// 主题颜色(优先使用主题色,否则使用默认色)
|
||||
const myBubbleBg = dynamicStyles?.outgoingBubbleColor || colors.chat.bubbleOutgoing;
|
||||
const screenBgColor = dynamicStyles?.chatBackgroundColor || colors.chat.screen;
|
||||
const cardBgColor = dynamicStyles?.cardBackgroundColor || colors.chat.card;
|
||||
const inputBgColor = dynamicStyles?.inputBackgroundColor || colors.chat.surfaceRaised;
|
||||
const panelBgColor = dynamicStyles?.panelBackgroundColor || colors.chat.surfaceMuted;
|
||||
const textPrimary = dynamicStyles?.textPrimaryColor || colors.chat.textPrimary;
|
||||
const textSecondary = dynamicStyles?.textSecondaryColor || colors.chat.textSecondary;
|
||||
|
||||
return StyleSheet.create({
|
||||
// 容器
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.chat.screen,
|
||||
backgroundColor: screenBgColor,
|
||||
},
|
||||
|
||||
// 头部
|
||||
headerContainer: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.chat.border,
|
||||
},
|
||||
@@ -39,7 +89,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm + 2,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
height: 56,
|
||||
},
|
||||
backButton: {
|
||||
@@ -69,7 +119,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
borderRadius: 10,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
groupAvatar: {
|
||||
@@ -91,13 +141,13 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
headerName: {
|
||||
fontWeight: '600',
|
||||
color: colors.text.primary,
|
||||
color: textPrimary,
|
||||
fontSize: 16,
|
||||
letterSpacing: -0.3,
|
||||
},
|
||||
memberCount: {
|
||||
fontSize: 12,
|
||||
color: colors.text.secondary,
|
||||
color: textSecondary,
|
||||
fontWeight: '400',
|
||||
},
|
||||
typingHint: {
|
||||
@@ -241,9 +291,9 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
// 消息气泡 - 微信风格:更小的圆角 + 更紧凑的内边距
|
||||
// 消息气泡 - 动态圆角支持
|
||||
messageBubbleOuter: {
|
||||
borderRadius: 12,
|
||||
borderRadius: messageRadius,
|
||||
minWidth: 44,
|
||||
shadowColor: colors.chat.shadow,
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
@@ -252,44 +302,44 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
elevation: 1,
|
||||
},
|
||||
myBubbleOuter: {
|
||||
borderBottomRightRadius: 4, // 微信的小尾巴更尖
|
||||
borderBottomRightRadius: Math.max(4, messageRadius * 0.3), // 微信的小尾巴更尖
|
||||
},
|
||||
theirBubbleOuter: {
|
||||
borderTopLeftRadius: 4,
|
||||
borderTopLeftRadius: Math.max(4, messageRadius * 0.3),
|
||||
},
|
||||
// 内层:更紧凑的微信风格内边距
|
||||
// 内层:动态圆角支持
|
||||
messageBubbleInner: {
|
||||
borderRadius: 12,
|
||||
borderRadius: messageRadius,
|
||||
overflow: 'hidden',
|
||||
paddingHorizontal: 10, // 微信:更窄的左右边距
|
||||
paddingVertical: 7, // 微信:更紧的上下边距
|
||||
minWidth: 44,
|
||||
},
|
||||
myBubbleInner: {
|
||||
backgroundColor: colors.chat.bubbleOutgoing,
|
||||
borderBottomRightRadius: 4,
|
||||
backgroundColor: myBubbleBg,
|
||||
borderBottomRightRadius: Math.max(4, messageRadius * 0.3),
|
||||
},
|
||||
theirBubbleInner: {
|
||||
backgroundColor: colors.chat.bubbleIncoming,
|
||||
borderTopLeftRadius: 4,
|
||||
backgroundColor: cardBgColor,
|
||||
borderTopLeftRadius: Math.max(4, messageRadius * 0.3),
|
||||
},
|
||||
recalledBubble: {
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.chat.border,
|
||||
borderStyle: 'dashed',
|
||||
},
|
||||
myBubbleText: {
|
||||
color: colors.chat.textPrimary, // 使用主题文字颜色
|
||||
fontSize: 18,
|
||||
lineHeight: 25,
|
||||
color: textPrimary, // 使用主题文字颜色
|
||||
fontSize,
|
||||
lineHeight: Math.round(fontSize * 1.4),
|
||||
letterSpacing: 0.1,
|
||||
fontWeight: '400',
|
||||
},
|
||||
theirBubbleText: {
|
||||
color: colors.chat.textPrimary, // 使用主题文字颜色
|
||||
fontSize: 18,
|
||||
lineHeight: 25,
|
||||
color: textPrimary, // 使用主题文字颜色
|
||||
fontSize,
|
||||
lineHeight: Math.round(fontSize * 1.4),
|
||||
letterSpacing: 0.1,
|
||||
fontWeight: '400',
|
||||
},
|
||||
@@ -362,7 +412,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
|
||||
// 输入框区域 - 与列表背景同色底栏,顶部细分隔
|
||||
inputWrapper: {
|
||||
backgroundColor: colors.chat.screen,
|
||||
backgroundColor: screenBgColor,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: 'rgba(0, 0, 0, 0.06)',
|
||||
},
|
||||
@@ -384,7 +434,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
inputInner: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.chat.surfaceRaised,
|
||||
backgroundColor: inputBgColor,
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: spacing.xs,
|
||||
paddingVertical: 4,
|
||||
@@ -518,7 +568,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
|
||||
// 面板
|
||||
panelWrapper: {
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.chat.border,
|
||||
shadowColor: colors.chat.shadow,
|
||||
@@ -529,11 +579,11 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
// 表情面板包装器 - 底部 tab 栏是白色,安全区域也用白色填充
|
||||
emojiPanelWrapper: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
},
|
||||
panelContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
},
|
||||
|
||||
// 表情面板
|
||||
@@ -578,12 +628,12 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
paddingVertical: spacing.sm + 2,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: colors.chat.borderHairline,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
},
|
||||
panelTabContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
borderRadius: 12,
|
||||
padding: 3,
|
||||
},
|
||||
@@ -595,7 +645,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
borderRadius: 10,
|
||||
},
|
||||
panelTabActive: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
shadowColor: colors.chat.shadow,
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.08,
|
||||
@@ -617,7 +667,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
},
|
||||
|
||||
// 自定义表情面板
|
||||
@@ -656,7 +706,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
margin: 4,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: colors.chat.surfaceInput,
|
||||
backgroundColor: inputBgColor,
|
||||
},
|
||||
stickerImage: {
|
||||
width: '100%',
|
||||
@@ -684,7 +734,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: spacing.sm + 2,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
shadowColor: colors.chat.shadow,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.06,
|
||||
@@ -709,14 +759,14 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
moreItemText: {
|
||||
fontSize: 12,
|
||||
color: colors.chat.textSecondary,
|
||||
color: textSecondary,
|
||||
fontWeight: '500',
|
||||
letterSpacing: 0.3,
|
||||
},
|
||||
// MorePanel 整体背景
|
||||
morePanelBackground: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
},
|
||||
|
||||
// @成员选择浮层 - 绝对定位浮在输入框上方
|
||||
@@ -724,7 +774,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
position: 'absolute',
|
||||
left: 12,
|
||||
right: 12,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderRadius: 18,
|
||||
shadowColor: colors.primary.main,
|
||||
shadowOffset: { width: 0, height: -3 },
|
||||
@@ -739,7 +789,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
// @成员选择面板
|
||||
mentionPanelContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderRadius: 18,
|
||||
},
|
||||
mentionPanelDragHandle: {
|
||||
@@ -774,7 +824,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
width: 26,
|
||||
height: 26,
|
||||
borderRadius: 13,
|
||||
backgroundColor: colors.chat.surfaceInput,
|
||||
backgroundColor: inputBgColor,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
@@ -851,7 +901,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
// 旧版菜单样式(保留兼容)
|
||||
menuContainer: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderRadius: 12,
|
||||
minWidth: 160,
|
||||
shadowColor: colors.chat.shadow,
|
||||
@@ -873,12 +923,12 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
menuItemText: {
|
||||
fontSize: 15,
|
||||
color: colors.text.primary,
|
||||
color: textPrimary,
|
||||
fontWeight: '500',
|
||||
},
|
||||
// 底部菜单容器
|
||||
bottomMenuContainer: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
paddingTop: spacing.md,
|
||||
@@ -891,7 +941,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
// 消息预览区域
|
||||
messagePreviewContainer: {
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
marginHorizontal: spacing.md,
|
||||
marginBottom: spacing.md,
|
||||
paddingHorizontal: spacing.md,
|
||||
@@ -934,12 +984,12 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
menuActionLabel: {
|
||||
fontSize: 13,
|
||||
color: colors.text.primary,
|
||||
color: textPrimary,
|
||||
fontWeight: '500',
|
||||
},
|
||||
// 取消按钮
|
||||
menuCancelButton: {
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
marginHorizontal: spacing.md,
|
||||
paddingVertical: spacing.md,
|
||||
borderRadius: 12,
|
||||
@@ -947,7 +997,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
},
|
||||
menuCancelText: {
|
||||
fontSize: 16,
|
||||
color: colors.text.primary,
|
||||
color: textPrimary,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
@@ -1037,7 +1087,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
zIndex: 1000,
|
||||
},
|
||||
overlayContent: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
padding: spacing.xl,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
@@ -1056,7 +1106,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
|
||||
// 表情包添加按钮(管理界面第一位)
|
||||
stickerAddButton: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderWidth: 1.5,
|
||||
borderColor: colors.primary.main,
|
||||
borderStyle: 'dashed',
|
||||
@@ -1075,7 +1125,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
|
||||
// 表情包管理按钮(表情面板第一位)
|
||||
stickerManageButton: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.chat.border,
|
||||
borderStyle: 'dashed',
|
||||
@@ -1146,7 +1196,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
|
||||
// 表情管理模态框
|
||||
manageModalContainer: {
|
||||
backgroundColor: colors.chat.surfaceMuted,
|
||||
backgroundColor: panelBgColor,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
overflow: 'hidden',
|
||||
@@ -1157,7 +1207,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.md,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.chat.border,
|
||||
},
|
||||
@@ -1183,7 +1233,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
manageInfoBar: {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.chat.border,
|
||||
},
|
||||
@@ -1204,7 +1254,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.md,
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: cardBgColor,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.chat.border,
|
||||
paddingBottom: 34, // 安全区域
|
||||
@@ -1279,5 +1329,6 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
|
||||
export function useChatScreenStyles() {
|
||||
const colors = useAppColors();
|
||||
return useMemo(() => createChatScreenStyles(colors), [colors]);
|
||||
const dynamicStyles = useChatDynamicStyles();
|
||||
return useMemo(() => createChatScreenStyles(colors, dynamicStyles), [colors, dynamicStyles]);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 胡萝卜BBS - 聊天个性化设置
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
@@ -23,19 +23,16 @@ import {
|
||||
} from '../../theme';
|
||||
import { Text } from '../../components/common';
|
||||
import { useResponsiveSpacing } from '../../hooks';
|
||||
import {
|
||||
useChatSettingsStore,
|
||||
useChatSettingsActions,
|
||||
CHAT_THEMES,
|
||||
} from '../../stores/chatSettingsStore';
|
||||
import { useCurrentUser } from '../../stores';
|
||||
|
||||
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
||||
const CARD_MAX_WIDTH = 720;
|
||||
|
||||
// 主题配置
|
||||
const THEMES = [
|
||||
{ id: 'default', name: '默认绿', primary: '#4CAF50', secondary: '#E8F5E9', bubble: '#DCF8C6', icon: '🏠' },
|
||||
{ id: 'yellow', name: '活力黄', primary: '#FFC107', secondary: '#FFF8E1', bubble: '#FFECB3', icon: '🐥' },
|
||||
{ id: 'blue', name: '清新蓝', primary: '#2196F3', secondary: '#E3F2FD', bubble: '#BBDEFB', icon: '☃️' },
|
||||
{ id: 'purple', name: '梦幻紫', primary: '#9C27B0', secondary: '#F3E5F5', bubble: '#E1BEE7', icon: '💎' },
|
||||
{ id: 'teal', name: '自然青', primary: '#009688', secondary: '#E0F2F1', bubble: '#B2DFDB', icon: '🦁' },
|
||||
] as const;
|
||||
|
||||
interface SliderProps {
|
||||
value: number;
|
||||
min: number;
|
||||
@@ -306,13 +303,22 @@ export const ChatSettingsScreen: React.FC = () => {
|
||||
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
||||
|
||||
// 状态
|
||||
const [fontSize, setFontSize] = useState(16);
|
||||
const [messageRadius, setMessageRadius] = useState(17);
|
||||
const [selectedTheme, setSelectedTheme] = useState(0);
|
||||
const [nightMode, setNightMode] = useState(false);
|
||||
// 获取当前用户信息
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
const currentTheme = THEMES[selectedTheme];
|
||||
// 从 store 获取状态
|
||||
const fontSize = useChatSettingsStore((s) => s.fontSize);
|
||||
const messageRadius = useChatSettingsStore((s) => s.messageRadius);
|
||||
const themeIndex = useChatSettingsStore((s) => s.themeIndex);
|
||||
const nightMode = useChatSettingsStore((s) => s.nightMode);
|
||||
|
||||
// 获取操作方法
|
||||
const { setFontSize, setMessageRadius, setTheme, toggleNightMode } = useChatSettingsActions();
|
||||
|
||||
const currentTheme = CHAT_THEMES[themeIndex];
|
||||
|
||||
// 用户显示名称
|
||||
const userName = currentUser?.nickname || currentUser?.username || '我';
|
||||
|
||||
// 渲染聊天预览
|
||||
const renderChatPreview = () => (
|
||||
@@ -320,7 +326,7 @@ export const ChatSettingsScreen: React.FC = () => {
|
||||
<Text style={styles.sectionTitle}>预览</Text>
|
||||
<View style={[styles.previewContainer, { backgroundColor: currentTheme.secondary }]}>
|
||||
<View style={[styles.previewMessage, { borderRadius: messageRadius }]}>
|
||||
<Text style={[styles.previewText, { fontSize }]}>MiMQy</Text>
|
||||
<Text style={[styles.previewText, { fontSize }]}>{userName}</Text>
|
||||
<Text style={[styles.previewText, { fontSize: fontSize - 2 }]}>
|
||||
早上好!👋
|
||||
</Text>
|
||||
@@ -331,9 +337,9 @@ export const ChatSettingsScreen: React.FC = () => {
|
||||
</View>
|
||||
<View style={[styles.previewReply, { borderRadius: messageRadius, backgroundColor: currentTheme.bubble }]}>
|
||||
<Text style={[styles.previewText, { fontSize }]}>
|
||||
现在是东京的早晨😎
|
||||
现在是威海的早晨😎
|
||||
</Text>
|
||||
<Text style={styles.previewTime}>AM12:57 ✓</Text>
|
||||
<Text style={styles.previewTime}>AM12:57</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -365,15 +371,15 @@ export const ChatSettingsScreen: React.FC = () => {
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.themeList}
|
||||
>
|
||||
{THEMES.map((theme, index) => (
|
||||
{CHAT_THEMES.map((theme, index) => (
|
||||
<TouchableOpacity
|
||||
key={theme.id}
|
||||
style={[
|
||||
styles.themeItem,
|
||||
selectedTheme === index && styles.themeItemActive,
|
||||
themeIndex === index && styles.themeItemActive,
|
||||
{ backgroundColor: theme.secondary },
|
||||
]}
|
||||
onPress={() => setSelectedTheme(index)}
|
||||
onPress={() => setTheme(index)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<View style={styles.themePreview}>
|
||||
@@ -450,7 +456,7 @@ export const ChatSettingsScreen: React.FC = () => {
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[styles.switch, nightMode && styles.switchActive]}
|
||||
onPress={() => setNightMode(!nightMode)}
|
||||
onPress={() => toggleNightMode()}
|
||||
>
|
||||
<View style={[styles.switchThumb, { alignSelf: nightMode ? 'flex-end' : 'flex-start' }]} />
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
201
src/stores/chatSettingsStore.ts
Normal file
201
src/stores/chatSettingsStore.ts
Normal 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,
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
27
src/stores/sessionStore.ts
Normal file
27
src/stores/sessionStore.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 会话状态管理
|
||||
*
|
||||
* 这是一个轻量级的 store,只存储当前用户的身份信息(userId)。
|
||||
* 它的目的是打破 authStore 和 callStore 之间的循环依赖:
|
||||
*
|
||||
* sessionStore (存储 userId)
|
||||
* ↑ ↑
|
||||
* authStore callStore
|
||||
*
|
||||
* authStore 在登录成功后设置 userId,callStore 从 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;
|
||||
Reference in New Issue
Block a user