feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
2026-04-25 17:27:22 +08:00
|
|
|
import { Alert, Linking } from 'react-native'
|
|
|
|
|
import { XuqmSDK, ImSDK, UpdateSDK } from '@xuqm/rn-sdk'
|
feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
import { demoApi, type UserProfile } from '../api/demo'
|
|
|
|
|
import { save, load, clearSession, K } from '../utils/storage'
|
2026-04-25 17:27:22 +08:00
|
|
|
import pluginMeta from '../../plugin.json'
|
feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
|
|
|
|
|
const APP_ID = 'ak_demo_chat'
|
2026-04-27 17:18:55 +08:00
|
|
|
const SERVER_URL = 'https://dev.xuqinmin.com'
|
feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
|
2026-04-27 19:00:54 +08:00
|
|
|
function buildImDbName(appId: string, userId: string): string {
|
|
|
|
|
return `xuqm_im_${appId}_${userId}`
|
|
|
|
|
}
|
|
|
|
|
|
feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
interface AuthState {
|
|
|
|
|
ready: boolean
|
|
|
|
|
userId: string | null
|
|
|
|
|
profile: UserProfile | null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface AuthContextValue extends AuthState {
|
|
|
|
|
login(userId: string, password: string): Promise<void>
|
|
|
|
|
register(userId: string, password: string, nickname: string): Promise<void>
|
|
|
|
|
logout(): Promise<void>
|
|
|
|
|
refreshProfile(): Promise<void>
|
|
|
|
|
updateProfile(data: Partial<Pick<UserProfile, 'nickname' | 'avatar' | 'gender'>>): Promise<void>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const AuthContext = createContext<AuthContextValue | null>(null)
|
|
|
|
|
|
|
|
|
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const [state, setState] = useState<AuthState>({ ready: false, userId: null, profile: null })
|
|
|
|
|
|
2026-04-27 19:00:54 +08:00
|
|
|
const initSDK = useCallback(async (profile: UserProfile, imToken: string) => {
|
|
|
|
|
const appId = profile.appId || APP_ID
|
|
|
|
|
await XuqmSDK.initialize({ appId, serverUrl: SERVER_URL })
|
|
|
|
|
await ImSDK.loginWithToken(profile.userId, imToken, buildImDbName(appId, profile.userId))
|
|
|
|
|
setState({ ready: true, userId: profile.userId, profile })
|
2026-04-25 17:27:22 +08:00
|
|
|
|
|
|
|
|
UpdateSDK.checkAppUpdate()
|
|
|
|
|
.then(update => {
|
|
|
|
|
if (update) {
|
|
|
|
|
Alert.alert(
|
|
|
|
|
'发现新版本',
|
|
|
|
|
`${update.versionName} 是否立即更新?`,
|
|
|
|
|
[
|
|
|
|
|
{ text: '稍后', style: 'cancel' },
|
2026-04-27 13:38:03 +08:00
|
|
|
{ text: '立即更新', onPress: () => update.downloadUrl && Linking.openURL(update.downloadUrl) },
|
2026-04-25 17:27:22 +08:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
UpdateSDK.checkRnUpdate(pluginMeta.moduleId).catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const restore = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const demoToken = await load<string>(K.DEMO_TOKEN)
|
|
|
|
|
const imToken = await load<string>(K.IM_TOKEN)
|
|
|
|
|
const profile = await load<UserProfile>(K.PROFILE)
|
|
|
|
|
if (demoToken && imToken && profile) {
|
2026-04-27 19:00:54 +08:00
|
|
|
await initSDK(profile, imToken)
|
feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
await clearSession()
|
|
|
|
|
}
|
|
|
|
|
setState(s => ({ ...s, ready: true }))
|
|
|
|
|
}
|
|
|
|
|
restore()
|
|
|
|
|
}, [initSDK])
|
|
|
|
|
|
|
|
|
|
const handleAuthResult = useCallback(async (result: { demoToken: string; imToken: string; profile: UserProfile }) => {
|
|
|
|
|
await save(K.DEMO_TOKEN, result.demoToken)
|
|
|
|
|
await save(K.IM_TOKEN, result.imToken)
|
|
|
|
|
await save(K.PROFILE, result.profile)
|
2026-04-27 19:00:54 +08:00
|
|
|
await initSDK(result.profile, result.imToken)
|
feat: complete production chat demo — all screens, real media, SDK remote init
- App.tsx: replaced dev console with AuthProvider + AppNavigator
- Auth: Login, Register, ResetPassword screens via demo-service
- Conversations: reactive WatermelonDB subscription with user profile enrichment
- Chat: SingleChat + GroupChat with full media (image/video/audio/file), revoke, pull-up load more
- Contacts: local contacts list + UserSearch with debounced fuzzy search
- Groups: GroupList, CreateGroup (fuzzy member picker), GroupMembers, GroupSettings
- Profile: view + EditProfile (nickname, gender)
- MessageSearch: local DB full-text search across conversations
- ChatInput: text, 20-emoji picker, image/video/audio/file send, tap-to-record audio
- DisconnectBanner: connection status with reconnect
- AuthContext: uses await XuqmSDK.initialize({ appId, serverUrl }) for remote config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:54 +08:00
|
|
|
}, [initSDK])
|
|
|
|
|
|
|
|
|
|
const login = useCallback(async (userId: string, password: string) => {
|
|
|
|
|
const result = await demoApi.login(userId, password)
|
|
|
|
|
await handleAuthResult(result)
|
|
|
|
|
}, [handleAuthResult])
|
|
|
|
|
|
|
|
|
|
const register = useCallback(async (userId: string, password: string, nickname: string) => {
|
|
|
|
|
const result = await demoApi.register(userId, password, nickname)
|
|
|
|
|
await handleAuthResult(result)
|
|
|
|
|
}, [handleAuthResult])
|
|
|
|
|
|
|
|
|
|
const logout = useCallback(async () => {
|
|
|
|
|
ImSDK.disconnect()
|
|
|
|
|
await clearSession()
|
|
|
|
|
setState({ ready: true, userId: null, profile: null })
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const refreshProfile = useCallback(async () => {
|
|
|
|
|
const profile = await demoApi.getProfile()
|
|
|
|
|
await save(K.PROFILE, profile)
|
|
|
|
|
setState(s => ({ ...s, profile }))
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
const updateProfile = useCallback(async (data: Partial<Pick<UserProfile, 'nickname' | 'avatar' | 'gender'>>) => {
|
|
|
|
|
const profile = await demoApi.updateProfile(data)
|
|
|
|
|
await save(K.PROFILE, profile)
|
|
|
|
|
setState(s => ({ ...s, profile }))
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<AuthContext.Provider value={{ ...state, login, register, logout, refreshProfile, updateProfile }}>
|
|
|
|
|
{children}
|
|
|
|
|
</AuthContext.Provider>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useAuth(): AuthContextValue {
|
|
|
|
|
const ctx = useContext(AuthContext)
|
|
|
|
|
if (!ctx) throw new Error('useAuth must be used inside AuthProvider')
|
|
|
|
|
return ctx
|
|
|
|
|
}
|