Files
frontend/src/screens/profile/UserProfileScreen.tsx

270 lines
8.5 KiB
TypeScript
Raw Normal View History

/**
* - Twitter/X
* self other
* Twitter/X
*
*/
import React, { useCallback, useMemo, useRef } from 'react';
import {
View,
RefreshControl,
ScrollView,
} from 'react-native';
import { FlashList, ListRenderItem } from '@shopify/flash-list';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useAppColors } from '../../theme';
import { Post } from '../../types';
import { PostCard, TabBar, UserProfileHeader } from '../../components/business';
import { Loading, EmptyState, ResponsiveContainer } from '../../components/common';
import { useResponsive } from '../../hooks';
import { useUserProfile, ProfileMode, TABS, TAB_ICONS, createSharedProfileStyles } from './useUserProfile';
interface UserProfileScreenProps {
mode: ProfileMode;
userId?: string;
hasHeader?: boolean;
}
export const UserProfileScreen: React.FC<UserProfileScreenProps> = ({ mode, userId, hasHeader = false }) => {
const colors = useAppColors();
const sharedStyles = useMemo(() => createSharedProfileStyles(colors), [colors]);
const { isDesktop, isTablet } = useResponsive();
const {
user,
posts,
favorites,
loading,
refreshing,
activeTab,
setActiveTab,
scrollBottomInset,
onRefresh,
handleFollow,
handlePostAction,
handleFollowingPress,
handleFollowersPress,
handleMessage,
handleBlock,
isBlocked,
isBlockedProfile,
handleSettings,
handleEditProfile,
isCurrentUser,
currentUser,
} = useUserProfile({ mode, userId, isDesktop, isTablet });
// 当前显示的帖子列表
const currentPosts = activeTab === 0 ? posts : favorites;
// 渲染帖子项
const renderPostItem = useCallback<ListRenderItem<Post>>(({ item, index }) => {
const isPostAuthor = currentUser?.id === item.author?.id;
const isLast = index === currentPosts.length - 1;
return (
<View style={[
sharedStyles.postWrapper,
isLast && sharedStyles.lastPost,
]}>
<PostCard
post={item}
onAction={(action) => handlePostAction(item, action)}
isPostAuthor={isPostAuthor}
/>
</View>
);
}, [currentUser?.id, handlePostAction, currentPosts.length]);
const postKeyExtractor = useCallback((item: Post) => item.id, []);
// 渲染空状态
const renderEmptyPosts = useCallback(() => {
if (loading) return <Loading />;
if (isBlockedProfile && activeTab === 0 && mode === 'other') {
return (
<EmptyState
title="已将该用户拉黑"
description="你已将此用户拉黑,不再显示其帖子"
icon="account-off-outline"
variant="modern"
/>
);
}
const emptyTitle = activeTab === 0
? (mode === 'self' ? '还没有帖子' : '这个用户还没有发布任何帖子')
: (mode === 'self' ? '还没有收藏' : '这个用户还没有收藏任何帖子');
const emptyDesc = activeTab === 0
? (mode === 'self' ? '分享你的想法,发布第一条帖子吧' : '')
: (mode === 'self' ? '发现喜欢的内容,点击收藏按钮保存' : '');
return (
<EmptyState
title={emptyTitle}
description={emptyDesc}
icon={activeTab === 0 ? 'file-document-edit-outline' : 'bookmark-heart-outline'}
variant="modern"
/>
);
}, [loading, activeTab, mode, isBlockedProfile]);
// 渲染用户信息头部(与 TabBar 分离,避免切换 tab 时重渲染)
const renderUserHeader = useMemo(() => {
if (!user) return null;
return (
<UserProfileHeader
user={user}
isCurrentUser={isCurrentUser}
isBlocked={isBlocked}
onFollow={handleFollow}
onSettings={handleSettings}
onEditProfile={handleEditProfile}
onMessage={handleMessage}
onBlock={handleBlock}
onFollowingPress={handleFollowingPress}
onFollowersPress={handleFollowersPress}
/>
);
}, [user, isCurrentUser, isBlocked, handleFollow, handleSettings, handleEditProfile, handleMessage, handleBlock, handleFollowingPress, handleFollowersPress]);
// 渲染 TabBar
const renderTabBar = useMemo(() => (
<View style={sharedStyles.tabBarContainer}>
<TabBar
tabs={TABS}
activeIndex={activeTab}
onTabChange={setActiveTab}
variant="modern"
icons={TAB_ICONS}
/>
</View>
), [activeTab, setActiveTab, sharedStyles.tabBarContainer]);
// 渲染 FlashList 头部(用户信息 + TabBar
const renderListHeader = useCallback(() => {
if (!user) return null;
return (
<>
{renderUserHeader}
{renderTabBar}
</>
);
}, [user, renderUserHeader, renderTabBar]);
// 未登录/用户不存在状态
if (mode === 'self' && !currentUser) {
return (
<SafeAreaView style={sharedStyles.container} edges={hasHeader ? ['bottom'] : ['top', 'bottom']}>
<EmptyState
title="未登录"
description="请先登录"
icon="account-off-outline"
/>
</SafeAreaView>
);
}
if (mode === 'other' && !user && !loading) {
return (
<SafeAreaView style={sharedStyles.container} edges={['bottom']}>
<EmptyState
title="用户不存在"
description="该用户可能已被删除"
icon="account-off-outline"
/>
</SafeAreaView>
);
}
const safeAreaEdges = hasHeader ? ['bottom'] : (mode === 'self' ? ['top', 'bottom'] : ['bottom']);
// 桌面端使用双栏布局
if (isDesktop || isTablet) {
return (
<SafeAreaView style={sharedStyles.container} edges={safeAreaEdges as any}>
<ResponsiveContainer maxWidth={1200}>
<View style={sharedStyles.desktopContainer}>
{/* 左侧:用户信息 */}
<View style={sharedStyles.desktopSidebar}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: scrollBottomInset }}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={[colors.primary.main]}
tintColor={colors.primary.main}
/>
}
>
{renderListHeader()}
</ScrollView>
</View>
{/* 右侧:帖子列表 */}
<View style={sharedStyles.desktopContent}>
<FlashList
data={currentPosts}
renderItem={renderPostItem}
keyExtractor={postKeyExtractor}
ListHeaderComponent={
<View style={sharedStyles.tabBarContainer}>
<TabBar
tabs={TABS}
activeIndex={activeTab}
onTabChange={setActiveTab}
variant="modern"
icons={TAB_ICONS}
/>
</View>
}
ListEmptyComponent={renderEmptyPosts}
contentContainerStyle={{ paddingBottom: scrollBottomInset }}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={[colors.primary.main]}
tintColor={colors.primary.main}
/>
}
drawDistance={250}
/>
</View>
</View>
</ResponsiveContainer>
</SafeAreaView>
);
}
// 移动端使用单栏布局
return (
<SafeAreaView style={sharedStyles.container} edges={safeAreaEdges as any}>
<FlashList
data={currentPosts}
renderItem={renderPostItem}
keyExtractor={postKeyExtractor}
ListHeaderComponent={renderListHeader}
ListEmptyComponent={renderEmptyPosts}
contentContainerStyle={{ paddingBottom: scrollBottomInset }}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={[colors.primary.main]}
tintColor={colors.primary.main}
/>
}
drawDistance={250}
/>
</SafeAreaView>
);
};
export default UserProfileScreen;