chore(deps): upgrade Expo SDK from 55 to 56
Some checks failed
Frontend CI / ota-android (push) Successful in 3m39s
Frontend CI / ota-ios (push) Successful in 3m40s
Frontend CI / build-and-push-web (push) Successful in 11m53s
Frontend CI / build-android-apk (push) Failing after 13m6s

Migrate from @react-navigation/native to expo-router navigation hooks across all screens.
Add polyfills module and apply LiveKit VideoView optional loading for Expo Go compatibility.
Improve background sync to skip when user is not logged in.
Enhance fetchUnreadCount to return typed totalUnread and systemUnread values.
This commit is contained in:
lafay
2026-06-01 22:56:37 +08:00
parent f39288f401
commit ad06881b85
21 changed files with 1893 additions and 2727 deletions

View File

@@ -9,7 +9,13 @@ import {
} from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Track, LocalVideoTrack, RemoteVideoTrack } from 'livekit-client';
import { VideoView } from '@livekit/react-native';
let VideoView: React.ComponentType<any> | null = null;
try {
VideoView = require('@livekit/react-native').VideoView;
} catch {
// WebRTC native module not available (e.g. Expo Go)
}
type VideoTrack = LocalVideoTrack | RemoteVideoTrack;
import { callStore } from '../../stores/call';
@@ -126,7 +132,7 @@ const CallScreen: React.FC = () => {
<View style={styles.background} />
{/* Remote video - full screen */}
{showRemoteVideo && remoteVideoTrack && (
{showRemoteVideo && remoteVideoTrack && VideoView && (
<VideoView
videoTrack={remoteVideoTrack}
style={styles.fullScreenVideo}
@@ -135,7 +141,7 @@ const CallScreen: React.FC = () => {
)}
{/* Local video - picture in picture */}
{showLocalVideo && localVideoTrack && (
{showLocalVideo && localVideoTrack && VideoView && (
<View style={styles.localVideoContainer}>
<VideoView
videoTrack={localVideoTrack}

View File

@@ -9,7 +9,14 @@ import {
} from 'react-native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Track, LocalVideoTrack, RemoteVideoTrack } from 'livekit-client';
import { VideoView } from '@livekit/react-native';
let VideoView: React.ComponentType<any> | null = null;
try {
VideoView = require('@livekit/react-native').VideoView;
} catch {
// @livekit/react-native not available on web
}
import { callStore } from '../../stores/call';
import { liveKitService } from '@/services/livekit';
@@ -119,7 +126,7 @@ const CallScreen: React.FC = () => {
<StatusBar barStyle="light-content" backgroundColor="transparent" translucent />
<View style={styles.background} />
{showRemoteVideo && remoteVideoTrack && (
{showRemoteVideo && remoteVideoTrack && VideoView && (
<VideoView
videoTrack={remoteVideoTrack}
style={styles.fullScreenVideo}
@@ -127,7 +134,7 @@ const CallScreen: React.FC = () => {
/>
)}
{showLocalVideo && localVideoTrack && (
{showLocalVideo && localVideoTrack && VideoView && (
<View style={styles.localVideoContainer}>
<VideoView
videoTrack={localVideoTrack}

8
src/polyfills.ts Normal file
View File

@@ -0,0 +1,8 @@
if (typeof globalThis.DOMException === 'undefined') {
(globalThis as any).DOMException = class DOMException extends Error {
constructor(message?: string, name?: string) {
super(message);
this.name = name || 'DOMException';
}
};
}

View File

@@ -21,7 +21,7 @@ import {
Dimensions,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useFocusEffect } from '@react-navigation/native';
import { useFocusEffect } from "expo-router/react-navigation";
import { useLocalSearchParams, useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import * as ImagePicker from 'expo-image-picker';

View File

@@ -26,8 +26,8 @@ import {
import { FlashList, ListRenderItem } from '@shopify/flash-list';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useRouter } from 'expo-router';
import { useIsFocused } from '@react-navigation/native';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useIsFocused } from "expo-router/react-navigation";
import { useBottomTabBarHeight } from "expo-router/js-tabs";
import { MaterialCommunityIcons } from '@expo/vector-icons';
import {
spacing,
@@ -842,25 +842,25 @@ export const MessageListScreen: React.FC = () => {
<View style={styles.chatArea}>
{showNotifications ? (
// 显示系统通知页面
<NotificationsScreen onBack={stableCloseNotifications} />
(<NotificationsScreen onBack={stableCloseNotifications} />)
) : selectedConversation ? (
// 显示选中会话的聊天内容
<ChatScreen
(<ChatScreen
isEmbedded
embeddedConversationId={String(selectedConversation.id)}
embeddedIsGroupChat={selectedConversation.type === 'group'}
embeddedGroupId={selectedConversation.group ? String(selectedConversation.group.id) : undefined}
embeddedGroupName={selectedConversation.group?.name}
onEmbeddedBack={stableClearSelectedConversation}
/>
/>)
) : (
// 默认占位符
<View style={styles.chatPlaceholder}>
(<View style={styles.chatPlaceholder}>
<View style={styles.chatPlaceholderContent}>
<MaterialCommunityIcons name="message-text-outline" size={64} color={colors.chat.iconMuted} />
<Text style={styles.chatPlaceholderText}></Text>
</View>
</View>
</View>)
)}
</View>
</View>
@@ -873,7 +873,7 @@ export const MessageListScreen: React.FC = () => {
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
{showNotifications ? (
// 显示系统通知页面,传入 onBack 回调
<NotificationsScreen onBack={stableCloseNotifications} />
(<NotificationsScreen onBack={stableCloseNotifications} />)
) : isSearchMode ? (
renderSearchMode()
) : isWideScreen ? (

View File

@@ -26,7 +26,7 @@ import {
} from 'react-native';
import { FlashList, ListRenderItem } from '@shopify/flash-list';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useIsFocused } from '@react-navigation/native';
import { useIsFocused } from "expo-router/react-navigation";
import { useRouter } from 'expo-router';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { spacing, borderRadius, useAppColors, useResolvedColorScheme, type AppColors } from '../../theme';

View File

@@ -17,7 +17,7 @@ import {
Image,
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { useNavigation } from "expo-router/react-navigation";
import { useRouter } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { MaterialCommunityIcons } from '@expo/vector-icons';

View File

@@ -23,7 +23,7 @@ import { runOnJS } from 'react-native-reanimated';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { blurActiveElement } from '../../infrastructure/platform';
import { useFocusEffect } from '@react-navigation/native';
import { useFocusEffect } from "expo-router/react-navigation";
import { useRouter } from 'expo-router';
import {
fontSizes,

View File

@@ -50,6 +50,11 @@ TaskManager.defineTask(BACKGROUND_SYNC_TASK, async () => {
* 执行消息同步
*/
async function syncMessages(): Promise<void> {
// 未登录时跳过同步,避免报"登录已过期"错误
const token = await api.getToken();
if (!token) {
return;
}
try {
// 同步会话未读数
const unreadData = await messageService.getUnreadCount();

View File

@@ -1,3 +1,4 @@
import '../../polyfills';
import { Room, RoomEvent, Track, ConnectionState, LocalParticipant, RemoteParticipant, RemoteTrackPublication, TrackPublication, Participant as LKParticipant } from 'livekit-client';
export interface LiveKitServiceConfig {

View File

@@ -208,7 +208,7 @@ class MessageManager {
return this.syncService.loadMoreMessages(conversationId, beforeSeq, limit);
}
async fetchUnreadCount(): Promise<void> {
async fetchUnreadCount(): Promise<{ totalUnread: number; systemUnread: number }> {
return this.syncService.fetchUnreadCount();
}

View File

@@ -30,7 +30,7 @@ export class MessageSyncService implements IMessageSyncService {
/** 正在加载会话列表下一页 */
private loadingMoreConversations = false;
/** fetchUnreadCount 去重:复用 in-flight promise */
private fetchUnreadCountPromise: Promise<void> | null = null;
private fetchUnreadCountPromise: Promise<{ totalUnread: number; systemUnread: number }> | null = null;
constructor(
getCurrentUserId: () => string | null,
@@ -352,19 +352,20 @@ export class MessageSyncService implements IMessageSyncService {
/**
* 获取未读数(去重:复用 in-flight promise
*/
async fetchUnreadCount(): Promise<void> {
async fetchUnreadCount(): Promise<{ totalUnread: number; systemUnread: number }> {
if (this.fetchUnreadCountPromise) {
return this.fetchUnreadCountPromise;
}
this.fetchUnreadCountPromise = this.doFetchUnreadCount();
try {
await this.fetchUnreadCountPromise;
const result = await this.fetchUnreadCountPromise;
return result;
} finally {
this.fetchUnreadCountPromise = null;
}
}
private async doFetchUnreadCount(): Promise<void> {
private async doFetchUnreadCount(): Promise<{ totalUnread: number; systemUnread: number }> {
const store = useMessageStore.getState();
try {
@@ -392,8 +393,10 @@ export class MessageSyncService implements IMessageSyncService {
// 服务端汇总未读为 0不主动清零本地未读等 fetchConversations 提供权威数据
// 避免后端缓存竞态导致的未读数抖动
return { totalUnread, systemUnread };
} catch (error) {
console.error('[MessageSyncService] 获取未读数失败:', error);
return { totalUnread: 0, systemUnread: 0 };
}
}