feat(Theme): enhance theming and UI consistency across components
- 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:
@@ -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]
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user