Files
frontend/src/screens/profile/BlockedUsersScreen.tsx
lafay b91e78c921
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m44s
Frontend CI / ota-android (push) Successful in 11m56s
Frontend CI / build-android-apk (push) Successful in 49m3s
refactor(PostCard, PostCardGrid, HomeScreen): enhance PostCard component and remove PostCardGrid
- Introduced a new PostCardRelativeTime component for dynamic time display, improving user experience with real-time updates.
- Refactored PostCard to utilize memoization for performance optimization and prevent unnecessary re-renders.
- Removed the PostCardGrid component to streamline the codebase, consolidating functionality within the PostCard component.
- Updated HomeScreen to leverage the new PostCard features, ensuring consistent post rendering and interaction handling.
2026-03-24 05:52:24 +08:00

179 lines
5.0 KiB
TypeScript

import React, { useCallback, useEffect, useState } from 'react';
import {
ActivityIndicator,
FlatList,
RefreshControl,
StyleSheet,
TouchableOpacity,
View,
Alert,
} from 'react-native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
import { navigationService } from '../../infrastructure/navigation/navigationService';
import { Avatar, Button, EmptyState, ResponsiveContainer, Text } from '../../components/common';
import { authService } from '../../services';
import { colors, spacing, borderRadius, shadows } from '../../theme';
import { User } from '../../types';
import { useResponsive } from '../../hooks';
export const BlockedUsersScreen: React.FC = () => {
const { isMobile } = useResponsive();
const insets = useSafeAreaInsets();
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [processingUserId, setProcessingUserId] = useState<string | null>(null);
// 底部间距,避免被 TabBar 遮挡
const listBottomInset = isMobile ? 64 + 24 + insets.bottom + spacing.md : spacing.md;
const loadBlockedUsers = useCallback(async () => {
try {
const response = await authService.getBlockedUsers(1, 100);
setUsers(response.list || []);
} catch (error) {
console.error('加载黑名单失败:', error);
} finally {
setLoading(false);
setRefreshing(false);
}
}, []);
useEffect(() => {
loadBlockedUsers();
}, [loadBlockedUsers]);
const onRefresh = useCallback(() => {
setRefreshing(true);
loadBlockedUsers();
}, [loadBlockedUsers]);
const handleUnblock = useCallback((user: User) => {
Alert.alert(
'取消拉黑',
`确定取消拉黑 ${user.nickname} 吗?`,
[
{ text: '取消', style: 'cancel' },
{
text: '确定',
onPress: async () => {
try {
setProcessingUserId(user.id);
const ok = await authService.unblockUser(user.id);
if (!ok) {
Alert.alert('失败', '取消拉黑失败,请稍后重试');
return;
}
setUsers(prev => prev.filter(u => u.id !== user.id));
} finally {
setProcessingUserId(null);
}
},
},
]
);
}, []);
const renderItem = ({ item }: { item: User }) => (
<TouchableOpacity
style={styles.item}
activeOpacity={0.75}
onPress={() => navigationService.navigate('UserProfile', { userId: item.id })}
>
<Avatar source={item.avatar} size={46} name={item.nickname} />
<View style={styles.content}>
<Text variant="body" style={styles.nickname} numberOfLines={1}>
{item.nickname}
</Text>
<Text variant="caption" color={colors.text.secondary} numberOfLines={1}>
@{item.username}
</Text>
</View>
<Button
title={processingUserId === item.id ? '处理中...' : '取消拉黑'}
size="sm"
variant="outline"
onPress={() => handleUnblock(item)}
disabled={processingUserId === item.id}
/>
</TouchableOpacity>
);
if (loading) {
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<View style={styles.loadingWrap}>
<ActivityIndicator size="large" color={colors.primary.main} />
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<ResponsiveContainer maxWidth={900}>
<FlatList
data={users}
keyExtractor={item => item.id}
renderItem={renderItem}
contentContainerStyle={[styles.listContent, users.length === 0 && styles.emptyContent, { paddingBottom: listBottomInset }]}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={[colors.primary.main]}
tintColor={colors.primary.main}
/>
}
ListEmptyComponent={
<EmptyState
title="黑名单为空"
description="你还没有拉黑任何用户"
icon="account-off-outline"
variant="modern"
/>
}
/>
</ResponsiveContainer>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.default,
},
loadingWrap: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
listContent: {
padding: spacing.md,
gap: spacing.sm,
},
emptyContent: {
flexGrow: 1,
justifyContent: 'center',
},
item: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background.paper,
borderRadius: borderRadius.lg,
padding: spacing.md,
...shadows.sm,
},
content: {
flex: 1,
marginLeft: spacing.md,
marginRight: spacing.sm,
},
nickname: {
fontWeight: '600',
},
});
export default BlockedUsersScreen;