refactor(database): unify database connections and add retry logic
Refactor LocalDataSource to reuse the shared database connection from database.ts, avoiding OPFS file handle conflicts. Add retry logic with exponential backoff for database open operations, particularly important for Web/OPFS environments. Also add dynamic theme support to chat input and message bubble styles, using theme colors instead of hardcoded values for better light/dark mode support. BREAKING CHANGE: Database initialization now requires userId to be passed explicitly for user-specific databases
This commit is contained in:
@@ -51,8 +51,8 @@ export const ChatInput: React.FC<ChatInputProps & {
|
||||
// 动态颜色
|
||||
const textPrimary = dynamicStyles.textPrimaryColor || colors.chat.textPrimary;
|
||||
const textSecondary = dynamicStyles.textSecondaryColor || colors.chat.textSecondary;
|
||||
const textMuted = '#999'; // 禁用状态颜色
|
||||
const textDisabled = '#CCC'; // 完全禁用颜色
|
||||
const textMuted = colors.chat.textPlaceholder || '#999'; // 禁用状态颜色
|
||||
const textDisabled = colors.chat.iconMuted || '#CCC'; // 完全禁用颜色
|
||||
|
||||
// 响应式布局
|
||||
const isWideScreen = useBreakpointGTE('lg');
|
||||
@@ -62,6 +62,37 @@ export const ChatInput: React.FC<ChatInputProps & {
|
||||
isWideScreen ? { maxWidth: 900, alignSelf: 'center' as const, width: '100%' as const } : null,
|
||||
]), [isWideScreen, styles.inputContainer]);
|
||||
|
||||
// 附件样式 - 动态生成以使用主题色
|
||||
const attachmentStyles = useMemo(() => StyleSheet.create({
|
||||
stripScroll: {
|
||||
maxHeight: 88,
|
||||
marginBottom: 6,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
stripContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 4,
|
||||
},
|
||||
thumbWrap: {
|
||||
position: 'relative',
|
||||
marginRight: 8,
|
||||
},
|
||||
thumb: {
|
||||
width: 72,
|
||||
height: 72,
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.chat.surfaceMuted || '#E8EAED',
|
||||
},
|
||||
removeBtn: {
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
right: -6,
|
||||
backgroundColor: colors.chat.card || '#FFF',
|
||||
borderRadius: 12,
|
||||
},
|
||||
}), [colors]);
|
||||
|
||||
const canSend =
|
||||
(!!inputText.trim() || pendingAttachments.length > 0) &&
|
||||
!isComposerBusy &&
|
||||
@@ -162,11 +193,11 @@ export const ChatInput: React.FC<ChatInputProps & {
|
||||
onPress={onToggleEmoji}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={activePanel === 'emoji' ? "keyboard" : "emoticon-happy-outline"}
|
||||
size={26}
|
||||
color={isDisabled ? textDisabled : (activePanel === 'emoji' ? colors.primary.main : textSecondary)}
|
||||
/>
|
||||
<MaterialCommunityIcons
|
||||
name={activePanel === 'emoji' ? "keyboard" : "emoticon-happy-outline"}
|
||||
size={26}
|
||||
color={isDisabled ? textDisabled : (activePanel === 'emoji' ? colors.primary.main : textSecondary)}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.inputBox}>
|
||||
@@ -212,11 +243,11 @@ export const ChatInput: React.FC<ChatInputProps & {
|
||||
onPress={onToggleMore}
|
||||
disabled={isDisabled || disableMore}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={activePanel === 'more' ? "close-circle" : "plus-circle-outline"}
|
||||
size={26}
|
||||
color={isDisabled || disableMore ? '#CCC' : (activePanel === 'more' ? colors.primary.main : '#666')}
|
||||
/>
|
||||
<MaterialCommunityIcons
|
||||
name={activePanel === 'more' ? "close-circle" : "plus-circle-outline"}
|
||||
size={26}
|
||||
color={isDisabled || disableMore ? textDisabled : (activePanel === 'more' ? colors.primary.main : textSecondary)}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
@@ -224,34 +255,4 @@ export const ChatInput: React.FC<ChatInputProps & {
|
||||
);
|
||||
};
|
||||
|
||||
const attachmentStyles = StyleSheet.create({
|
||||
stripScroll: {
|
||||
maxHeight: 88,
|
||||
marginBottom: 6,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
stripContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 4,
|
||||
},
|
||||
thumbWrap: {
|
||||
position: 'relative',
|
||||
marginRight: 8,
|
||||
},
|
||||
thumb: {
|
||||
width: 72,
|
||||
height: 72,
|
||||
borderRadius: 10,
|
||||
backgroundColor: '#E8EAED',
|
||||
},
|
||||
removeBtn: {
|
||||
position: 'absolute',
|
||||
top: -6,
|
||||
right: -6,
|
||||
backgroundColor: '#FFF',
|
||||
borderRadius: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default ChatInput;
|
||||
|
||||
@@ -41,12 +41,14 @@ export function useChatDynamicStyles() {
|
||||
messageRadius,
|
||||
// 夜间模式时使用系统暗色,否则使用主题色
|
||||
outgoingBubbleColor: isNightMode ? colors.chat.bubbleOutgoing : theme.bubble,
|
||||
// 对方消息气泡背景色 - 使用纯白以区分header和背景
|
||||
theirBubbleBackgroundColor: isNightMode ? colors.chat.card : '#FFFFFF',
|
||||
// 背景色
|
||||
chatBackgroundColor: isNightMode ? colors.chat.screen : theme.secondary,
|
||||
// 卡片/头部背景色
|
||||
cardBackgroundColor: isNightMode ? colors.chat.card : theme.card,
|
||||
// 输入框背景色
|
||||
inputBackgroundColor: isNightMode ? colors.chat.surfaceRaised : theme.inputBg,
|
||||
// 卡片/头部背景色 - 使用主题次级色(稍深)
|
||||
cardBackgroundColor: isNightMode ? colors.chat.card : theme.secondary,
|
||||
// 输入框背景色 - 使用纯白以保持对比度
|
||||
inputBackgroundColor: isNightMode ? colors.chat.surfaceRaised : '#FFFFFF',
|
||||
// 面板背景色
|
||||
panelBackgroundColor: isNightMode ? colors.chat.surfaceMuted : theme.panelBg,
|
||||
// 主要文字颜色
|
||||
@@ -54,12 +56,13 @@ export function useChatDynamicStyles() {
|
||||
// 次要文字颜色
|
||||
textSecondaryColor: isNightMode ? colors.chat.textSecondary : theme.textSecondary,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
fontSize?: number;
|
||||
messageRadius?: number;
|
||||
outgoingBubbleColor?: string;
|
||||
theirBubbleBackgroundColor?: string;
|
||||
chatBackgroundColor?: string;
|
||||
cardBackgroundColor?: string;
|
||||
inputBackgroundColor?: string;
|
||||
@@ -72,6 +75,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
|
||||
// 主题颜色(优先使用主题色,否则使用默认色)
|
||||
const myBubbleBg = dynamicStyles?.outgoingBubbleColor || colors.chat.bubbleOutgoing;
|
||||
const theirBubbleBg = dynamicStyles?.theirBubbleBackgroundColor || colors.chat.card;
|
||||
const screenBgColor = dynamicStyles?.chatBackgroundColor || colors.chat.screen;
|
||||
const cardBgColor = dynamicStyles?.cardBackgroundColor || colors.chat.card;
|
||||
const inputBgColor = dynamicStyles?.inputBackgroundColor || colors.chat.surfaceRaised;
|
||||
@@ -329,7 +333,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
borderBottomRightRadius: Math.max(4, messageRadius * 0.3),
|
||||
},
|
||||
theirBubbleInner: {
|
||||
backgroundColor: cardBgColor,
|
||||
backgroundColor: theirBubbleBg,
|
||||
borderTopLeftRadius: Math.max(4, messageRadius * 0.3),
|
||||
},
|
||||
recalledBubble: {
|
||||
@@ -423,7 +427,7 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
inputWrapper: {
|
||||
backgroundColor: screenBgColor,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: 'rgba(0, 0, 0, 0.06)',
|
||||
borderTopColor: colors.chat.border,
|
||||
},
|
||||
inputContainer: {
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
@@ -53,7 +53,7 @@ function createEmbeddedChatStyles(colors: AppColors) {
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm,
|
||||
backgroundColor: colors.background.paper,
|
||||
backgroundColor: colors.background.default,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderBottomColor: colors.divider,
|
||||
shadowColor: 'transparent',
|
||||
|
||||
Reference in New Issue
Block a user