refactor(message): improve message synchronization and hook reliability
Some checks failed
Frontend CI / ota-android (push) Successful in 1m29s
Frontend CI / build-and-push-web (push) Failing after 1m31s
Frontend CI / ota-ios (push) Successful in 4m4s
Frontend CI / build-android-apk (push) Successful in 29m5s

Refactor the message management system to address synchronization issues and improve performance through request deduplication and enhanced lifecycle management.

- Implement in-flight promise tracking in `MessageSyncService` to prevent redundant network requests for conversations and messages.
- Enhance `useMessages` hook with `useFocusEffect` to trigger automatic synchronization when a chat screen regains focus.
- Add `forceSync` capability to `MessageManager` and `MessageSyncService` to allow manual overrides of loading states during re-entry.
- Consolidate WebSocket synchronization logic in `WSMessageHandler` using a throttled and deduplicated trigger mechanism.
- Clean up unused styles and deprecated hooks (`baseHooks.ts`, `bubbleStyles.ts`, `inputStyles.ts`).
- Update `metro.config.js` and `package.json` to include `@expo/ui` and optimize resolver settings.
This commit is contained in:
2026-06-03 10:31:46 +08:00
parent 2e6912dddf
commit 2e2f6e3467
13 changed files with 396 additions and 1115 deletions

View File

@@ -1,317 +0,0 @@
/**
* 消息气泡样式工具
* 参考 Element X 设计,实现动态圆角和现代化气泡样式
*/
import { StyleSheet, ViewStyle, TextStyle } from 'react-native';
import { spacing, type AppColors } from '../../../../theme';
import { useChatSettingsStore } from '../../../../stores/settings';
// 默认圆角,但会从 store 读取
export const BUBBLE_RADIUS = 16;
// 获取动态圆角值
export function useBubbleRadius(): number {
return useChatSettingsStore((s) => s.messageRadius);
}
export function getBubbleColors(colors: AppColors, outgoingBubbleColor?: string) {
return {
outgoing: {
background: outgoingBubbleColor || colors.chat.bubbleOutgoing,
text: colors.chat.textPrimary,
},
incoming: {
background: colors.chat.bubbleIncoming,
text: colors.chat.textPrimary,
},
replyHighlight: {
background: colors.chat.replyTint,
borderLeft: colors.chat.replyBorder,
},
};
}
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;
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 getMessageGroupPosition = (
index: number,
messages: Array<{ sender_id: string }>,
currentUserId: string
): MessageGroupPosition => {
const currentMessage = messages[index];
if (!currentMessage) return 'single';
const prevMessage = index > 0 ? messages[index - 1] : null;
const nextMessage = index < messages.length - 1 ? messages[index + 1] : null;
const isSameSenderAsPrev = prevMessage && prevMessage.sender_id === currentMessage.sender_id;
const isSameSenderAsNext = nextMessage && nextMessage.sender_id === currentMessage.sender_id;
if (!isSameSenderAsPrev && !isSameSenderAsNext) {
return 'single';
} else if (!isSameSenderAsPrev && isSameSenderAsNext) {
return 'first';
} else if (isSameSenderAsPrev && isSameSenderAsNext) {
return 'middle';
} else {
return 'last';
}
};
export const shouldShowSenderInfo = (
index: number,
messages: Array<{ sender_id: string }>
): boolean => {
if (index === 0) return true;
const currentMessage = messages[index];
const prevMessage = messages[index - 1];
return prevMessage.sender_id !== currentMessage.sender_id;
};
export const getBubbleBaseStyle = (isMe: boolean, colors: AppColors, outgoingBubbleColor?: string): ViewStyle => {
const bc = getBubbleColors(colors, outgoingBubbleColor);
return {
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm + 4,
minWidth: 60,
maxWidth: '75%',
backgroundColor: isMe ? bc.outgoing.background : bc.incoming.background,
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.06,
shadowRadius: 2,
elevation: 2,
};
};
// 使用动态字号的 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,
lineHeight: Math.round(fontSize * 1.4),
letterSpacing: 0.2,
fontWeight: '400',
};
};
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,
paddingVertical: spacing.sm + 4,
minWidth: 60,
},
outgoing: {
backgroundColor: bc.outgoing.background,
},
incoming: {
backgroundColor: bc.incoming.background,
},
text: {
fontSize,
lineHeight: Math.round(fontSize * 1.4),
letterSpacing: 0.2,
fontWeight: '400',
},
shadow: {
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.06,
shadowRadius: 2,
elevation: 2,
},
longPressShadow: {
shadowColor: colors.chat.shadow,
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.12,
shadowRadius: 8,
elevation: 6,
},
replyHighlight: {
borderLeftWidth: 3,
borderLeftColor: colors.primary.main,
backgroundColor: bc.replyHighlight.background,
},
recalled: {
backgroundColor: colors.chat.surfaceMuted,
borderWidth: 1,
borderColor: colors.chat.border,
borderStyle: 'dashed',
borderRadius: 12,
},
systemNotice: {
alignItems: 'center',
marginVertical: spacing.sm,
},
systemNoticeText: {
fontSize: 12,
color: colors.chat.textSecondary,
backgroundColor: colors.chat.surfaceMuted,
paddingHorizontal: spacing.md,
paddingVertical: spacing.xs,
borderRadius: 12,
overflow: 'hidden',
},
});
}
export default {
BUBBLE_RADIUS,
getBubbleColors,
getBubbleBorderRadius,
getMessageGroupPosition,
shouldShowSenderInfo,
getBubbleBaseStyle,
getBubbleTextStyle,
createBubbleStyles,
};