feat(APKUpdate): implement APK update check and enhance build workflow
- Added `expo-intent-launcher` dependency to support APK launching functionality. - Introduced `APKUpdateBootstrap` component to check for APK updates during app initialization. - Enhanced build workflow in `build.yml` to resolve runtime version and upload APK to the updates server. - Updated `HomeScreen` and `TabsLayout` to manage tab visibility and scroll behavior based on user interactions. - Refactored message bubble styles for improved UI consistency and user experience.
This commit is contained in:
@@ -26,7 +26,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||
import { useAppColors, useResolvedColorScheme, spacing, borderRadius, shadows, type AppColors } from '../../theme';
|
||||
import { Post } from '../../types';
|
||||
import { useUserStore, useHomeTabBarVisibilityStore } from '../../stores';
|
||||
import { useUserStore, useHomeTabBarVisibilityStore, useHomeTabPressStore } from '../../stores';
|
||||
import { useCurrentUser } from '../../stores/authStore';
|
||||
import { channelService, postService } from '../../services';
|
||||
import { PostCard, TabBar, SearchBar } from '../../components/business';
|
||||
@@ -212,9 +212,14 @@ export const HomeScreen: React.FC = () => {
|
||||
|
||||
const setBottomTabBarHiddenByScroll = useHomeTabBarVisibilityStore((s) => s.setBottomTabBarHiddenByScroll);
|
||||
const bottomTabBarHiddenByScroll = useHomeTabBarVisibilityStore((s) => s.bottomTabBarHiddenByScroll);
|
||||
const homeTabPressCount = useHomeTabPressStore((s) => s.pressCount);
|
||||
const homeListScrollYRef = useRef(0);
|
||||
const tabBarIdleRestoreTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// FlatList 和 ScrollView 的 ref,用于滚动到顶部
|
||||
const flatListRef = useRef<FlatList<Post> | null>(null);
|
||||
const scrollViewRef = useRef<ScrollView | null>(null);
|
||||
|
||||
const clearTabBarIdleRestoreTimer = useCallback(() => {
|
||||
if (tabBarIdleRestoreTimerRef.current != null) {
|
||||
clearTimeout(tabBarIdleRestoreTimerRef.current);
|
||||
@@ -269,6 +274,19 @@ export const HomeScreen: React.FC = () => {
|
||||
}
|
||||
}, [showSearch, clearTabBarIdleRestoreTimer, setBottomTabBarHiddenByScroll]);
|
||||
|
||||
// 监听首页 Tab 点击事件,滚动到顶部
|
||||
useEffect(() => {
|
||||
if (homeTabPressCount > 0) {
|
||||
// 滚动 FlatList 到顶部
|
||||
flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||
// 滚动 ScrollView 到顶部(网格模式)
|
||||
scrollViewRef.current?.scrollTo({ y: 0, animated: true });
|
||||
// 重置底部 Tab 栏状态
|
||||
setBottomTabBarHiddenByScroll(false);
|
||||
homeListScrollYRef.current = 0;
|
||||
}
|
||||
}, [homeTabPressCount, setBottomTabBarHiddenByScroll]);
|
||||
|
||||
/** 横向胶囊条滚动位置:切换频道刷新列表时不重置 */
|
||||
const capsuleHScrollRef = useRef<ScrollView | null>(null);
|
||||
const capsuleScrollXRef = useRef(0);
|
||||
@@ -819,6 +837,7 @@ export const HomeScreen: React.FC = () => {
|
||||
// 移动端使用瀑布流布局(2列)
|
||||
return (
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
style={styles.waterfallScroll}
|
||||
contentContainerStyle={[
|
||||
styles.waterfallContainer,
|
||||
@@ -899,6 +918,7 @@ export const HomeScreen: React.FC = () => {
|
||||
// 移动端和宽屏都使用单列 FlatList,宽屏下居中显示
|
||||
return (
|
||||
<FlatList
|
||||
ref={flatListRef}
|
||||
data={displayPosts}
|
||||
renderItem={renderPostList}
|
||||
keyExtractor={keyExtractor}
|
||||
|
||||
@@ -404,7 +404,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
||||
>
|
||||
<Avatar
|
||||
source={senderInfo.avatar}
|
||||
size={36}
|
||||
size={40}
|
||||
name={senderInfo.nickname}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
@@ -418,7 +418,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
||||
>
|
||||
<Avatar
|
||||
source={otherUser?.avatar || null}
|
||||
size={36}
|
||||
size={40}
|
||||
name={otherUser?.nickname || ''}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
@@ -452,7 +452,7 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
||||
<View style={styles.avatarWrapper}>
|
||||
<Avatar
|
||||
source={currentUser?.avatar || null}
|
||||
size={36}
|
||||
size={40}
|
||||
name={currentUser?.nickname || ''}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -847,41 +847,36 @@ function createSegmentStyles(colors: AppColors) {
|
||||
marginTop: 2,
|
||||
},
|
||||
|
||||
// 文本 - Telegram风格:统一深色字体
|
||||
// 文本 - 柔和风格:深灰色文字 + 正常字重
|
||||
textContent: {
|
||||
fontSize: 16,
|
||||
lineHeight: 23,
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 17.5,
|
||||
lineHeight: 25,
|
||||
letterSpacing: 0.1,
|
||||
fontWeight: '400',
|
||||
},
|
||||
textMe: {
|
||||
color: colors.chat.textPrimary,
|
||||
color: '#2A2A2A',
|
||||
},
|
||||
textOther: {
|
||||
color: colors.chat.textPrimary,
|
||||
color: '#2A2A2A',
|
||||
},
|
||||
|
||||
// @提及 - Telegram风格:蓝色高亮
|
||||
// @提及 - 微信/QQ 风格:更精致的高亮
|
||||
atText: {
|
||||
fontSize: 16,
|
||||
lineHeight: 23,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 2,
|
||||
lineHeight: 22,
|
||||
fontWeight: '500',
|
||||
},
|
||||
atTextMe: {
|
||||
color: '#4A88C7',
|
||||
backgroundColor: 'rgba(74, 136, 199, 0.12)',
|
||||
borderRadius: 4,
|
||||
color: '#1A5F9E', // 深蓝色,在绿色背景上更清晰
|
||||
},
|
||||
atTextOther: {
|
||||
color: colors.chat.link,
|
||||
backgroundColor: 'rgba(74, 136, 199, 0.12)',
|
||||
borderRadius: 4,
|
||||
},
|
||||
atHighlight: {
|
||||
backgroundColor: 'rgba(74, 136, 199, 0.2)',
|
||||
backgroundColor: 'rgba(74, 136, 199, 0.15)',
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 4,
|
||||
paddingHorizontal: 3,
|
||||
},
|
||||
|
||||
// 图片 - QQ风格:保持原始宽高比
|
||||
|
||||
@@ -201,7 +201,7 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
|
||||
// 头像
|
||||
// 头像 - 更大一点
|
||||
avatarWrapper: {
|
||||
marginHorizontal: 2,
|
||||
shadowColor: colors.chat.shadow,
|
||||
@@ -226,43 +226,43 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
senderName: {
|
||||
fontSize: 13,
|
||||
color: colors.chat.link,
|
||||
fontSize: 14,
|
||||
color: '#555555', // 柔和的深灰色
|
||||
marginBottom: 4,
|
||||
marginLeft: 4,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
// 消息气泡 - 外层极轻阴影(纯文字气泡更接近扁平 IM)
|
||||
// 消息气泡 - 微信风格:更小的圆角 + 更紧凑的内边距
|
||||
messageBubbleOuter: {
|
||||
borderRadius: 16,
|
||||
minWidth: 60,
|
||||
borderRadius: 12,
|
||||
minWidth: 44,
|
||||
shadowColor: colors.chat.shadow,
|
||||
shadowOffset: { width: 0, height: 0.5 },
|
||||
shadowOpacity: 0.04,
|
||||
shadowRadius: 1,
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.06,
|
||||
shadowRadius: 2,
|
||||
elevation: 1,
|
||||
},
|
||||
myBubbleOuter: {
|
||||
borderBottomRightRadius: 4,
|
||||
borderBottomRightRadius: 4, // 微信的小尾巴更尖
|
||||
},
|
||||
theirBubbleOuter: {
|
||||
borderTopLeftRadius: 4,
|
||||
},
|
||||
// 内层:背景 + 裁剪,防止回复条/@ 高亮在圆角外露出颜色
|
||||
// 内层:更紧凑的微信风格内边距
|
||||
messageBubbleInner: {
|
||||
borderRadius: 16,
|
||||
borderRadius: 12,
|
||||
overflow: 'hidden',
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.sm + 4,
|
||||
minWidth: 60,
|
||||
paddingHorizontal: 10, // 微信:更窄的左右边距
|
||||
paddingVertical: 7, // 微信:更紧的上下边距
|
||||
minWidth: 44,
|
||||
},
|
||||
myBubbleInner: {
|
||||
backgroundColor: colors.chat.replyTint,
|
||||
backgroundColor: colors.chat.bubbleOutgoing,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
theirBubbleInner: {
|
||||
backgroundColor: colors.chat.card,
|
||||
backgroundColor: colors.chat.bubbleIncoming,
|
||||
borderTopLeftRadius: 4,
|
||||
},
|
||||
recalledBubble: {
|
||||
@@ -272,17 +272,17 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
borderStyle: 'dashed',
|
||||
},
|
||||
myBubbleText: {
|
||||
color: colors.chat.textPrimary, // 主文案
|
||||
fontSize: 16,
|
||||
lineHeight: 23,
|
||||
letterSpacing: 0.2,
|
||||
color: '#2A2A2A',
|
||||
fontSize: 18, // 更大字体
|
||||
lineHeight: 25, // 相应调大行高
|
||||
letterSpacing: 0.1,
|
||||
fontWeight: '400',
|
||||
},
|
||||
theirBubbleText: {
|
||||
color: colors.chat.textPrimary, // 主文案
|
||||
fontSize: 16,
|
||||
lineHeight: 23,
|
||||
letterSpacing: 0.2,
|
||||
color: '#2A2A2A',
|
||||
fontSize: 18,
|
||||
lineHeight: 25,
|
||||
letterSpacing: 0.1,
|
||||
fontWeight: '400',
|
||||
},
|
||||
// 选中状态
|
||||
@@ -312,21 +312,21 @@ export function createChatScreenStyles(colors: AppColors) {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
|
||||
// 图片消息 - QQ风格:更大的图片,更明显的圆角
|
||||
// 图片消息 - QQ/微信风格:更大的圆角,更柔和的阴影
|
||||
imageBubble: {
|
||||
borderRadius: 18,
|
||||
overflow: 'hidden',
|
||||
shadowColor: colors.chat.shadow,
|
||||
shadowOffset: { width: 0, height: 3 },
|
||||
shadowOpacity: 0.15,
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 6,
|
||||
elevation: 5,
|
||||
elevation: 4,
|
||||
},
|
||||
myImageBubble: {
|
||||
borderBottomRightRadius: 6, // 右下角尖,箭头在右下
|
||||
borderBottomRightRadius: 8,
|
||||
},
|
||||
theirImageBubble: {
|
||||
borderTopLeftRadius: 6, // 左上角尖,箭头在左上
|
||||
borderTopLeftRadius: 8,
|
||||
},
|
||||
messageImage: {
|
||||
width: 220,
|
||||
|
||||
Reference in New Issue
Block a user