XuqmGroup-RNChatDemo/src/screens/contact/UserSearchScreen.tsx

117 行
4.6 KiB
TypeScript

import React, { useState, useCallback, useRef } from 'react'
import {
View, Text, TextInput, FlatList, StyleSheet, TouchableOpacity,
SafeAreaView, ActivityIndicator, Alert,
} 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'
type Nav = NativeStackNavigationProp<RootStackParams>
export default function UserSearchScreen() {
const navigation = useNavigation<Nav>()
const [keyword, setKeyword] = useState('')
const [results, setResults] = useState<UserProfile[]>([])
const [loading, setLoading] = useState(false)
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const search = useCallback((text: string) => {
setKeyword(text)
if (debounceRef.current) clearTimeout(debounceRef.current)
if (!text.trim()) { setResults([]); return }
debounceRef.current = setTimeout(async () => {
setLoading(true)
try {
const res = await demoApi.searchUsers(text.trim())
setResults(res)
} catch {
setResults([])
} finally {
setLoading(false)
}
}, 300)
}, [])
const addContact = async (user: UserProfile) => {
try {
await ImSDK.addFriend(user.userId)
const current = (await load<UserProfile[]>(K.CONTACTS)) ?? []
if (!current.find(c => c.userId === user.userId)) {
await save(K.CONTACTS, [...current, user])
}
Alert.alert('已添加为好友')
} catch {
Alert.alert('添加失败')
}
}
const openChat = (user: UserProfile) => {
navigation.navigate('SingleChat', { targetId: user.userId, targetName: user.nickname, targetAvatar: user.avatar })
}
return (
<SafeAreaView style={styles.root}>
<View style={styles.searchBar}>
<TextInput
style={styles.input}
placeholder="搜索用户名或昵称"
value={keyword}
onChangeText={search}
autoFocus
clearButtonMode="while-editing"
/>
{loading && <ActivityIndicator style={styles.spinner} color="#07C160" />}
</View>
<FlatList
data={results}
keyExtractor={u => u.userId}
renderItem={({ item }) => {
const letter = (item.nickname || item.userId).charAt(0).toUpperCase()
return (
<View style={styles.row}>
<TouchableOpacity style={styles.rowBody} onPress={() => openChat(item)}>
<View style={styles.avatar}><Text style={styles.avatarText}>{letter}</Text></View>
<View>
<Text style={styles.name}>{item.nickname}</Text>
<Text style={styles.uid}>@{item.userId}</Text>
</View>
</TouchableOpacity>
<TouchableOpacity style={styles.addBtn} onPress={() => addContact(item)}>
<Text style={styles.addBtnText}>+ </Text>
</TouchableOpacity>
</View>
)
}}
ItemSeparatorComponent={() => <View style={styles.sep} />}
ListEmptyComponent={
keyword.trim() && !loading
? <View style={styles.empty}><Text style={styles.emptyText}></Text></View>
: null
}
/>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
root: { flex: 1, backgroundColor: '#f5f5f5' },
searchBar: { flexDirection: 'row', alignItems: 'center', margin: 12, backgroundColor: '#fff', borderRadius: 8, paddingHorizontal: 12, borderWidth: 1, borderColor: '#e0e0e0' },
input: { flex: 1, fontSize: 16, paddingVertical: 10 },
spinner: { marginLeft: 8 },
row: { flexDirection: 'row', alignItems: 'center', padding: 12, backgroundColor: '#fff' },
rowBody: { flex: 1, flexDirection: 'row', alignItems: 'center' },
avatar: { width: 44, height: 44, borderRadius: 8, backgroundColor: '#07C160', alignItems: 'center', justifyContent: 'center', marginRight: 12 },
avatarText: { color: '#fff', fontSize: 18, fontWeight: '600' },
name: { fontSize: 16, fontWeight: '500', color: '#111' },
uid: { fontSize: 13, color: '#888' },
addBtn: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#07C160', borderRadius: 6 },
addBtnText: { color: '#fff', fontSize: 13, fontWeight: '600' },
sep: { height: StyleSheet.hairlineWidth, backgroundColor: '#e0e0e0', marginLeft: 68 },
empty: { alignItems: 'center', paddingTop: 60 },
emptyText: { color: '#bbb', fontSize: 15 },
})