diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index cc4a7d7..e5c19c8 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -1,7 +1,9 @@ import React, { createContext, useCallback, useContext, useEffect, useState } from 'react' -import { XuqmSDK, ImSDK } from '@xuqm/rn-sdk' +import { Alert, Linking } from 'react-native' +import { XuqmSDK, ImSDK, UpdateSDK } from '@xuqm/rn-sdk' import { demoApi, type UserProfile } from '../api/demo' import { save, load, clearSession, K } from '../utils/storage' +import pluginMeta from '../../plugin.json' const APP_ID = 'ak_demo_chat' const SERVER_URL = 'https://sentry.xuqinmin.com' @@ -29,6 +31,23 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { await XuqmSDK.initialize({ appId: APP_ID, serverUrl: SERVER_URL }) await ImSDK.loginWithToken(userId, imToken, 'xuqm_im') setState({ ready: true, userId, profile }) + + UpdateSDK.checkAppUpdate() + .then(update => { + if (update) { + Alert.alert( + '发现新版本', + `${update.versionName} 是否立即更新?`, + [ + { text: '稍后', style: 'cancel' }, + { text: '立即更新', onPress: () => Linking.openURL(update.downloadUrl) }, + ], + ) + } else { + UpdateSDK.checkRnUpdate(pluginMeta.moduleId).catch(() => {}) + } + }) + .catch(() => {}) }, []) useEffect(() => { diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index 381afaf..d1572eb 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -22,6 +22,7 @@ import GroupMembersScreen from '../screens/group/GroupMembersScreen' import GroupSettingsScreen from '../screens/group/GroupSettingsScreen' import EditProfileScreen from '../screens/profile/EditProfileScreen' import MessageSearchScreen from '../screens/chat/MessageSearchScreen' +import DisconnectBanner from '../components/DisconnectBanner' const AuthStack = createNativeStackNavigator() const Tab = createBottomTabNavigator() @@ -55,6 +56,7 @@ function MainTabs() { function AppStack() { return ( + ({ title: route.params.targetName })} /> diff --git a/src/screens/chat/GroupChatScreen.tsx b/src/screens/chat/GroupChatScreen.tsx index 74d981a..e781645 100644 --- a/src/screens/chat/GroupChatScreen.tsx +++ b/src/screens/chat/GroupChatScreen.tsx @@ -10,7 +10,6 @@ import { useAuth } from '../../context/AuthContext' import type { RootStackParams } from '../../navigation/types' import { MessageBubble } from '../../components/MessageBubble' import ChatInput from '../../components/ChatInput' -import DisconnectBanner from '../../components/DisconnectBanner' type Props = NativeStackScreenProps @@ -85,7 +84,6 @@ export default function GroupChatScreen({ route, navigation }: Props) { return ( - @@ -86,7 +85,6 @@ export default function SingleChatScreen({ route, navigation }: Props) { return ( - () const [contacts, setContacts] = useState([]) + const [loading, setLoading] = useState(false) - useEffect(() => { - load(K.CONTACTS).then(c => { if (c) setContacts(c) }) + const fetchContacts = useCallback(async () => { + setLoading(true) + try { + const friendIds = await ImSDK.listFriends() + const profiles: UserProfile[] = [] + await Promise.all( + friendIds.map(async (id) => { + try { + const results = await demoApi.searchUsers(id) + const match = results.find(u => u.userId === id) + if (match) profiles.push(match) + } catch {/* skip individual failures */} + }), + ) + setContacts(profiles) + await save(K.CONTACTS, profiles) + } catch { + // network failed — load from local cache + const cached = await load(K.CONTACTS) + if (cached) setContacts(cached) + } finally { + setLoading(false) + } }, []) + useFocusEffect( + useCallback(() => { + fetchContacts() + }, [fetchContacts]), + ) + const openChat = (user: UserProfile) => { navigation.navigate('SingleChat', { targetId: user.userId, targetName: user.nickname, targetAvatar: user.avatar }) } @@ -50,18 +79,21 @@ export default function ContactsScreen() { + {loading && } u.userId} renderItem={({ item }) => openChat(item)} />} ItemSeparatorComponent={() => } ListEmptyComponent={ - - 还没有联系人 - navigation.navigate('UserSearch')}> - 搜索添加 - - + !loading ? ( + + 还没有联系人 + navigation.navigate('UserSearch')}> + 搜索添加 + + + ) : null } /> @@ -75,6 +107,7 @@ const styles = StyleSheet.create({ headerActions: { flexDirection: 'row', gap: 12 }, headerBtn: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#07C160', borderRadius: 6 }, headerBtnText: { color: '#fff', fontSize: 13, fontWeight: '600' }, + loadingIndicator: { marginVertical: 8 }, row: { flexDirection: 'row', alignItems: 'center', padding: 12, backgroundColor: '#fff' }, avatar: { width: 46, height: 46, borderRadius: 8, backgroundColor: '#07C160', alignItems: 'center', justifyContent: 'center', marginRight: 12 }, avatarText: { color: '#fff', fontSize: 18, fontWeight: '600' }, diff --git a/src/screens/contact/UserSearchScreen.tsx b/src/screens/contact/UserSearchScreen.tsx index 4e8018f..292399f 100644 --- a/src/screens/contact/UserSearchScreen.tsx +++ b/src/screens/contact/UserSearchScreen.tsx @@ -5,6 +5,7 @@ import { } from 'react-native' import { useNavigation } from '@react-navigation/native' import type { NativeStackNavigationProp } from '@react-navigation/native-stack' +import { ImSDK } from '@xuqm/rn-sdk' import { demoApi, type UserProfile } from '../../api/demo' import { load, save, K } from '../../utils/storage' import type { RootStackParams } from '../../navigation/types' @@ -37,13 +38,14 @@ export default function UserSearchScreen() { const addContact = async (user: UserProfile) => { try { + await ImSDK.addFriend(user.userId) const current = (await load(K.CONTACTS)) ?? [] if (!current.find(c => c.userId === user.userId)) { await save(K.CONTACTS, [...current, user]) } - Alert.alert('已添加', `${user.nickname} 已添加到通讯录`) + Alert.alert('已添加为好友') } catch { - Alert.alert('失败', '添加失败,请重试') + Alert.alert('添加失败') } }