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

135 行
4.5 KiB
TypeScript

import React, { useCallback, useState } from 'react'
import {
View, Text, FlatList, StyleSheet, TouchableOpacity, SafeAreaView,
ActivityIndicator, Alert,
} from 'react-native'
import { useFocusEffect } from '@react-navigation/native'
import { ImSDK, type FriendRequest } from '@xuqm/rn-sdk'
import type { UserProfile } from '../../api/demo'
import { toDemoUserProfile } from '../../utils/userProfiles'
function RequestRow({
request,
profile,
onAccept,
onReject,
}: {
request: FriendRequest
profile?: UserProfile
onAccept(): void
onReject(): void
}) {
const name = profile?.nickname || request.fromUserId
return (
<View style={styles.row}>
<View style={styles.body}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.uid}>@{request.fromUserId}</Text>
{!!request.remark && <Text style={styles.remark}>{request.remark}</Text>}
<Text style={styles.time}>{request.status}</Text>
</View>
<View style={styles.actions}>
<TouchableOpacity style={[styles.actionBtn, styles.acceptBtn]} onPress={onAccept}>
<Text style={styles.actionText}></Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.actionBtn, styles.rejectBtn]} onPress={onReject}>
<Text style={styles.actionText}></Text>
</TouchableOpacity>
</View>
</View>
)
}
export default function FriendRequestsScreen() {
const [requests, setRequests] = useState<FriendRequest[]>([])
const [loading, setLoading] = useState(true)
const [profiles, setProfiles] = useState<Record<string, UserProfile>>({})
const load = useCallback(async () => {
setLoading(true)
try {
const list = await ImSDK.listFriendRequests('incoming')
setRequests(list)
const nextProfiles: Record<string, UserProfile> = {}
await Promise.all(list.map(async (req) => {
try {
const result = await ImSDK.searchUsers(req.fromUserId)
const match = result.find(u => u.userId === req.fromUserId)
if (match) nextProfiles[req.fromUserId] = toDemoUserProfile(match)
} catch {
/* ignore */
}
}))
setProfiles(nextProfiles)
} catch {
setRequests([])
setProfiles({})
} finally {
setLoading(false)
}
}, [])
useFocusEffect(useCallback(() => { load() }, [load]))
const handleAccept = async (request: FriendRequest) => {
try {
await ImSDK.acceptFriendRequest(request.id)
await load()
} catch (e: any) {
Alert.alert('处理失败', e?.message ?? '请稍后重试')
}
}
const handleReject = async (request: FriendRequest) => {
try {
await ImSDK.rejectFriendRequest(request.id)
await load()
} catch (e: any) {
Alert.alert('处理失败', e?.message ?? '请稍后重试')
}
}
return (
<SafeAreaView style={styles.root}>
{loading ? (
<ActivityIndicator style={{ flex: 1 }} color="#07C160" />
) : (
<FlatList
data={requests}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<RequestRow
request={item}
profile={profiles[item.fromUserId]}
onAccept={() => handleAccept(item)}
onReject={() => handleReject(item)}
/>
)}
ItemSeparatorComponent={() => <View style={styles.sep} />}
ListEmptyComponent={<View style={styles.empty}><Text style={styles.emptyText}></Text></View>}
contentContainerStyle={requests.length === 0 ? styles.emptyContainer : undefined}
/>
)}
</SafeAreaView>
)
}
const styles = StyleSheet.create({
root: { flex: 1, backgroundColor: '#f5f5f5' },
row: { flexDirection: 'row', padding: 12, backgroundColor: '#fff', alignItems: 'center' },
body: { flex: 1, paddingRight: 8 },
name: { fontSize: 16, fontWeight: '600', color: '#111' },
uid: { fontSize: 13, color: '#888', marginTop: 2 },
remark: { fontSize: 13, color: '#555', marginTop: 4 },
time: { fontSize: 12, color: '#999', marginTop: 4 },
actions: { gap: 8 },
actionBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 6 },
acceptBtn: { backgroundColor: '#07C160' },
rejectBtn: { backgroundColor: '#ff3b30' },
actionText: { color: '#fff', fontSize: 13, fontWeight: '600' },
sep: { height: StyleSheet.hairlineWidth, backgroundColor: '#e0e0e0', marginLeft: 12 },
emptyContainer: { flexGrow: 1 },
empty: { flex: 1, alignItems: 'center', justifyContent: 'center' },
emptyText: { color: '#bbb', fontSize: 15 },
})