Files
frontend/src/screens/message/components/ChatScreen/bubbleStyles.ts
lafay a005fb0a15 feat: add QR code login and enhance chat experience
- Add QR code login flow with scanner and confirmation screens
- Implement swipe-to-reply in chat with SwipeableMessageBubble
- Replace FlatList with FlashList for chat performance optimization
- Add Schedule stack navigator with CourseDetail screen support
- Configure deep linking for QR code login (carrotbbs://qrcode/login/:sessionId)
- Update branding from 胡萝卜 to 萝卜社区
- Display dynamic app version in settings
2026-03-20 19:28:42 +08:00

270 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 消息气泡样式工具
* 参考 Element X 设计,实现动态圆角和现代化气泡样式
*/
import { StyleSheet, ViewStyle, TextStyle } from 'react-native';
import { colors, spacing } from '../../../../theme';
// 气泡圆角大小(参考 Element X: 12pt
export const BUBBLE_RADIUS = 16;
// 气泡颜色方案(参考 Element X 的语义化颜色)
export const bubbleColors = {
// 自己发送的消息
outgoing: {
background: '#E8E8E8', // Element X 风格:灰色系
text: '#1A1A1A',
},
// 收到的消息
incoming: {
background: '#FFFFFF',
text: '#1A1A1A',
},
// 被回复的消息高亮
replyHighlight: {
background: 'rgba(255, 107, 53, 0.08)',
borderLeft: '#FF6B35',
},
};
// 消息在组中的位置类型
export type MessageGroupPosition = 'single' | 'first' | 'middle' | 'last';
/**
* 根据消息在组中的位置获取圆角样式
* 参考 Element X 的动态圆角逻辑
*/
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): ViewStyle => ({
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm + 4,
minWidth: 60,
maxWidth: '75%',
backgroundColor: isMe ? bubbleColors.outgoing.background : bubbleColors.incoming.background,
// Element X 风格的微妙阴影
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.06,
shadowRadius: 2,
elevation: 2,
});
/**
* 获取文本样式
*/
export const getBubbleTextStyle = (isMe: boolean): TextStyle => ({
color: isMe ? bubbleColors.outgoing.text : bubbleColors.incoming.text,
fontSize: 16,
lineHeight: 23,
letterSpacing: 0.2,
fontWeight: '400',
});
/**
* 消息气泡样式集合
*/
export const bubbleStyles = StyleSheet.create({
// 基础气泡
bubble: {
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm + 4,
minWidth: 60,
},
// 自己发送的消息
outgoing: {
backgroundColor: bubbleColors.outgoing.background,
},
// 收到的消息
incoming: {
backgroundColor: bubbleColors.incoming.background,
},
// 文本样式
text: {
fontSize: 16,
lineHeight: 23,
letterSpacing: 0.2,
fontWeight: '400',
},
// 阴影样式Element X 风格)
shadow: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.06,
shadowRadius: 2,
elevation: 2,
},
// 长按反馈阴影
longPressShadow: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.12,
shadowRadius: 8,
elevation: 6,
},
// 被回复消息的高亮边框
replyHighlight: {
borderLeftWidth: 3,
borderLeftColor: colors.primary.main,
backgroundColor: bubbleColors.replyHighlight.background,
},
// 已撤回消息
recalled: {
backgroundColor: '#F5F7FA',
borderWidth: 1,
borderColor: '#E8E8E8',
borderStyle: 'dashed',
borderRadius: 12,
},
// 系统通知
systemNotice: {
alignItems: 'center',
marginVertical: spacing.sm,
},
systemNoticeText: {
fontSize: 12,
color: '#8E8E93',
backgroundColor: 'rgba(142, 142, 147, 0.12)',
paddingHorizontal: spacing.md,
paddingVertical: spacing.xs,
borderRadius: 12,
overflow: 'hidden',
},
});
export default {
BUBBLE_RADIUS,
bubbleColors,
getBubbleBorderRadius,
getMessageGroupPosition,
shouldShowSenderInfo,
getBubbleBaseStyle,
getBubbleTextStyle,
bubbleStyles,
};