feat(Theme): enhance theming and UI consistency across components
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 8m15s
Frontend CI / ota-android (push) Successful in 10m56s
Frontend CI / build-android-apk (push) Successful in 1h3m22s

- Updated app.json to set userInterfaceStyle to automatic for improved theme adaptability.
- Refactored layout components to utilize useAppColors for dynamic theming, ensuring consistent color usage.
- Introduced SystemChrome component to manage system UI background color based on theme.
- Enhanced TabsLayout, ProfileStackLayout, and other components to leverage new theming structure.
- Improved QRCodeScanner, SearchBar, and CommentItem styles to align with the updated theme system.
- Consolidated styles in SystemMessageItem and TabBar for better maintainability and visual coherence.
This commit is contained in:
lafay
2026-03-25 05:16:54 +08:00
parent 90d834695f
commit 4ee3079b9f
86 changed files with 6777 additions and 5890 deletions

View File

@@ -15,20 +15,12 @@ import {
} from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Avatar, Text, ImageGridItem } from '../../../../components/common';
import { chatScreenStyles as baseStyles } from './styles';
import { useChatScreenStyles } from './styles';
import { useResponsive, useBreakpointGTE } from '../../../../hooks/useResponsive';
import { MessageBubbleProps, SenderInfo, MenuPosition, GroupMessage } from './types';
import { MessageSegmentsRenderer } from './SegmentRenderer';
import { MessageSegment, ImageSegmentData, extractTextFromSegments } from '../../../../types/dto';
import { SwipeableMessageBubble } from './SwipeableMessageBubble';
import {
getBubbleBorderRadius,
getMessageGroupPosition,
shouldShowSenderInfo,
bubbleColors,
bubbleStyles,
} from './bubbleStyles';
// 获取屏幕宽度
const { width: SCREEN_WIDTH } = Dimensions.get('window');
@@ -61,7 +53,8 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
}) => {
const bubbleRef = useRef<View>(null);
const pressPositionRef = useRef({ x: 0, y: 0 });
const baseStyles = useChatScreenStyles();
// 响应式布局
const { width } = useResponsive();
const isWideScreen = useBreakpointGTE('lg');
@@ -89,7 +82,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
height: isWideScreen ? 280 : 220,
},
});
}, [maxContentWidth, isWideScreen]);
}, [maxContentWidth, isWideScreen, baseStyles]);
// 系统通知消息特殊处理
// 支持两种判断方式:
@@ -236,26 +229,46 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
}
};
const renderBubbleShell = useCallback(
(
child: React.ReactNode,
innerExtra: (object | undefined | null | false)[] = []
) => (
<View
style={[
styles.messageBubbleOuter,
isMe ? styles.myBubbleOuter : styles.theirBubbleOuter,
]}
>
<View
style={[
styles.messageBubbleInner,
isMe ? styles.myBubbleInner : styles.theirBubbleInner,
...innerExtra.filter(Boolean),
]}
>
{child}
</View>
</View>
),
[isMe, styles]
);
// 渲染消息内容
const renderMessageContent = () => {
// 撤回消息
if (isRecalled) {
return (
<View style={[
styles.messageBubble,
isMe ? styles.myBubble : styles.theirBubble,
styles.recalledBubble,
]}>
<Text
style={[
isMe ? styles.myBubbleText : styles.theirBubbleText,
styles.recalledText,
]}
selectable={false}
>
</Text>
</View>
return renderBubbleShell(
<Text
style={[
isMe ? styles.myBubbleText : styles.theirBubbleText,
styles.recalledText,
]}
selectable={false}
>
</Text>,
[styles.recalledBubble]
);
}
@@ -282,71 +295,60 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
// 如果没有 segments显示空内容或错误提示
if (!hasSegments) {
return (
<View style={[
styles.messageBubble,
isMe ? styles.myBubble : styles.theirBubble,
]}>
<Text
style={[
isMe ? styles.myBubbleText : styles.theirBubbleText,
{ opacity: 0.5 },
]}
selectable={false}
>
[]
</Text>
</View>
return renderBubbleShell(
<Text
style={[
isMe ? styles.myBubbleText : styles.theirBubbleText,
{ opacity: 0.5 },
]}
selectable={false}
>
[]
</Text>
);
}
// 检查是否有回复,用于调整气泡样式
const hasReply = segments.some(s => s.type === 'reply');
return (
<View style={[
styles.messageBubble,
isMe ? styles.myBubble : styles.theirBubble,
hasReply && segmentStyles.replyBubble,
isPureImageMessage && segmentStyles.pureImageBubble,
]}>
<MessageSegmentsRenderer
segments={segments}
isMe={isMe}
currentUserId={currentUserId}
memberMap={memberMap}
replyMessage={getReplyMessage()}
getSenderInfo={getSenderInfo}
onAtPress={() => undefined}
onReplyPress={onReplyPress}
onImagePress={(url) => {
// 查找点击的图片索引
const clickIndex = imageSegments.findIndex(
img => img.url === url || img.thumbnail_url === url
);
if (clickIndex !== -1 && onImagePress) {
onImagePress(imageSegments, clickIndex);
}
}}
onImageLongPress={(position?: { x: number; y: number }) => {
// 图片长按触发和消息气泡一样的菜单
// 如果有位置信息,直接使用;否则使用气泡位置
if (position) {
onLongPress(message, {
x: 0,
y: 0,
width: 0,
height: 0,
pressX: position.x,
pressY: position.y,
});
} else {
handleLongPress();
}
}}
onLinkPress={() => undefined}
/>
</View>
return renderBubbleShell(
<MessageSegmentsRenderer
segments={segments}
isMe={isMe}
currentUserId={currentUserId}
memberMap={memberMap}
replyMessage={getReplyMessage()}
getSenderInfo={getSenderInfo}
onAtPress={() => undefined}
onReplyPress={onReplyPress}
onImagePress={(url) => {
// 查找点击的图片索引
const clickIndex = imageSegments.findIndex(
img => img.url === url || img.thumbnail_url === url
);
if (clickIndex !== -1 && onImagePress) {
onImagePress(imageSegments, clickIndex);
}
}}
onImageLongPress={(position?: { x: number; y: number }) => {
// 图片长按触发和消息气泡一样的菜单
// 如果有位置信息,直接使用;否则使用气泡位置
if (position) {
onLongPress(message, {
x: 0,
y: 0,
width: 0,
height: 0,
pressX: position.x,
pressY: position.y,
});
} else {
handleLongPress();
}
}}
onLinkPress={() => undefined}
/>,
[hasReply && segmentStyles.replyBubble, isPureImageMessage && segmentStyles.pureImageBubble]
);
};