import React, { useState, useCallback, useMemo } from 'react'; import { View, StyleSheet, TextInput, TouchableOpacity, Alert, ActivityIndicator, Clipboard, FlatList, RefreshControl, } from 'react-native'; import { useRouter } from 'expo-router'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { spacing, borderRadius, fontSizes, useAppColors, type AppColors } from '../../theme'; import Avatar from '../../components/common/Avatar'; import Text from '../../components/common/Text'; import { groupService } from '../../services/groupService'; import { groupManager } from '../../stores/groupManager'; import { GroupResponse, JoinType } from '../../types/dto'; import { useCursorPagination } from '../../hooks/useCursorPagination'; import { EmptyState } from '../../components/common'; function createJoinGroupStyles(colors: AppColors) { return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background.default, padding: spacing.md, }, heroCard: { backgroundColor: colors.background.paper, borderRadius: borderRadius.xl, padding: spacing.lg, marginBottom: spacing.md, borderWidth: 0.5, borderColor: colors.divider + '40', }, heroIconWrap: { width: 48, height: 48, borderRadius: 24, backgroundColor: colors.primary.light + '15', alignItems: 'center', justifyContent: 'center', marginBottom: spacing.md, }, heroTitle: { marginBottom: spacing.xs, fontWeight: '800', fontSize: fontSizes.xl + 1, letterSpacing: 0.3, }, tip: { lineHeight: 22, fontWeight: '400', }, formCard: { backgroundColor: colors.background.paper, borderRadius: borderRadius.xl, padding: spacing.lg, flex: 1, borderWidth: 0.5, borderColor: colors.divider + '40', }, label: { marginBottom: spacing.xs, fontWeight: '700', fontSize: fontSizes.sm + 1, }, input: { flex: 1, borderWidth: 1.5, borderColor: colors.divider + '80', borderRadius: borderRadius.lg, backgroundColor: colors.background.default, paddingHorizontal: spacing.md, paddingVertical: spacing.md, color: colors.text.primary, fontSize: fontSizes.md, }, searchRow: { flexDirection: 'row', alignItems: 'center', marginBottom: spacing.md, }, searchBtn: { width: 46, height: 46, borderRadius: borderRadius.lg, backgroundColor: colors.primary.main, alignItems: 'center', justifyContent: 'center', marginLeft: spacing.sm, }, searchResultSection: { marginBottom: spacing.md, }, sectionTitle: { marginBottom: spacing.sm, fontWeight: '800', fontSize: fontSizes.md + 1, letterSpacing: 0.3, }, listSection: { flex: 1, }, groupCard: { borderWidth: 0.5, borderColor: colors.divider + '40', borderRadius: borderRadius.lg, padding: spacing.md, backgroundColor: colors.background.default, marginBottom: spacing.md, }, groupHeader: { flexDirection: 'row', alignItems: 'center', }, groupMeta: { marginLeft: spacing.md, flex: 1, }, groupName: { marginBottom: spacing.xs, fontWeight: '700', fontSize: fontSizes.md + 1, letterSpacing: 0.3, }, groupDesc: { marginTop: spacing.sm, lineHeight: 20, fontWeight: '400', }, groupInfoRow: { marginTop: spacing.sm, marginBottom: spacing.xs, flexDirection: 'row', justifyContent: 'space-between', }, groupNoRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: spacing.md, }, copyBtn: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: spacing.sm, paddingVertical: 4, borderRadius: borderRadius.sm, backgroundColor: colors.primary.light + '15', }, copyBtnText: { marginLeft: 4, fontWeight: '600', }, submitBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', borderRadius: borderRadius.lg, backgroundColor: colors.primary.main, minHeight: 46, }, submitBtnDisabled: { opacity: 0.5, }, submitText: { marginLeft: spacing.xs, fontWeight: '700', fontSize: fontSizes.md, }, emptyText: { marginTop: spacing.sm, textAlign: 'center', fontWeight: '500', }, loadingFooter: { paddingVertical: spacing.md, alignItems: 'center', }, loadMoreBtn: { paddingVertical: spacing.md, alignItems: 'center', }, noMoreText: { textAlign: 'center', paddingVertical: spacing.md, fontWeight: '500', color: colors.text.hint, }, }); } const JoinGroupScreen: React.FC = () => { const colors = useAppColors(); const styles = useMemo(() => createJoinGroupStyles(colors), [colors]); const router = useRouter(); const [keyword, setKeyword] = useState(''); const [searching, setSearching] = useState(false); const [joiningGroupId, setJoiningGroupId] = useState(null); const [searchedGroup, setSearchedGroup] = useState(null); const [searched, setSearched] = useState(false); // 使用游标分页 Hook 管理群组列表 const { list: groups, isLoading, isRefreshing, hasMore, loadMore, refresh, error, } = useCursorPagination( async ({ cursor, pageSize }) => { return await groupService.getGroupsCursor({ cursor, page_size: pageSize, }); }, { pageSize: 20 } ); const getJoinTypeText = (joinType: JoinType) => { if (joinType === 0) return '允许加入'; if (joinType === 1) return '需要审批'; return '禁止加入'; }; const handleSearch = async () => { const trimmed = keyword.trim(); if (!trimmed) { Alert.alert('提示', '请输入群ID进行搜索'); return; } setSearching(true); setSearched(true); try { const result = await groupManager.getGroup(trimmed, true); setSearchedGroup(result); } catch (error: any) { setSearchedGroup(null); const message = error?.response?.data?.message || error?.message || ''; if (String(message).includes('不存在') || error?.response?.status === 404) { Alert.alert('未找到', '未搜索到该群聊,请确认群ID是否正确'); } else { Alert.alert('搜索失败', '请稍后重试'); } } finally { setSearching(false); } }; const handleJoin = async (group: GroupResponse) => { if (!group?.id) return; setJoiningGroupId(String(group.id)); try { await groupService.joinGroup(group.id); Alert.alert('成功', '操作已提交', [ { text: '确定', onPress: () => router.back(), }, ]); } catch (error: any) { const message = error?.response?.data?.message || error?.message || '操作失败,请稍后重试'; Alert.alert('操作失败', String(message)); } finally { setJoiningGroupId(null); } }; const handleCopyGroupId = (groupId: string) => { Clipboard.setString(groupId); Alert.alert('已复制', '群号已复制到剪贴板'); }; const formatGroupNo = (id: string) => { const raw = id; if (raw.length <= 12) return raw; return `${raw.slice(0, 6)}...${raw.slice(-4)}`; }; const renderGroupItem = ({ item: group }: { item: GroupResponse }) => { const isJoining = joiningGroupId === String(group.id); return ( {group.name} {!!group.description && ( {group.description} )} 成员 {group.member_count}/{group.max_members} {getJoinTypeText(group.join_type)} 群号:{formatGroupNo(group.id)} handleCopyGroupId(group.id)}> 复制 handleJoin(group)} disabled={isJoining} > {isJoining ? ( ) : ( <> 申请入群 )} ); }; const renderEmptyList = () => { if (isLoading) return null; return ( ); }; const renderSearchResult = () => { if (!searched) return null; if (searchedGroup) { return ( 搜索结果 {renderGroupItem({ item: searchedGroup })} ); } if (!searching) { return ( 暂无搜索结果,请检查群ID后重试 ); } return null; }; return ( 搜索群聊 输入群 ID 搜索后,你可以先查看群资料,再决定是否申请加入。 搜索群聊(群ID) {searching ? ( ) : ( )} {/* 搜索结果 */} {renderSearchResult()} {/* 群组列表 */} 推荐群组 String(item.id)} refreshControl={ } onEndReached={loadMore} onEndReachedThreshold={0.3} ListEmptyComponent={renderEmptyList} ListFooterComponent={ isLoading ? ( ) : hasMore ? ( 加载更多 ) : groups.length > 0 ? ( 没有更多群组了 ) : null } showsVerticalScrollIndicator={false} /> ); }; export default JoinGroupScreen;