2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 个人主页 ProfileScreen - 美化版(响应式适配)
|
|
|
|
|
|
* 胡萝卜BBS - 当前用户个人主页
|
|
|
|
|
|
* 采用现代卡片式设计,优化视觉层次和交互体验
|
|
|
|
|
|
* 支持桌面端双栏布局
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useCallback, useMemo } from 'react';
|
|
|
|
|
|
import {
|
|
|
|
|
|
View,
|
|
|
|
|
|
StyleSheet,
|
|
|
|
|
|
RefreshControl,
|
|
|
|
|
|
Animated,
|
|
|
|
|
|
ScrollView,
|
|
|
|
|
|
} from 'react-native';
|
|
|
|
|
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
|
|
|
import { useNavigation } from '@react-navigation/native';
|
|
|
|
|
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
|
|
|
|
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
|
|
|
|
|
|
import { colors, spacing } from '../../theme';
|
|
|
|
|
|
import { Post } from '../../types';
|
|
|
|
|
|
import { useAuthStore, useUserStore } from '../../stores';
|
|
|
|
|
|
import { postService } from '../../services';
|
|
|
|
|
|
import { UserProfileHeader, PostCard, TabBar } from '../../components/business';
|
|
|
|
|
|
import { Loading, EmptyState, Text } from '../../components/common';
|
|
|
|
|
|
import { ResponsiveContainer } from '../../components/common';
|
|
|
|
|
|
import { useResponsive } from '../../hooks';
|
|
|
|
|
|
import { ProfileStackParamList, HomeStackParamList, RootStackParamList } from '../../navigation/types';
|
|
|
|
|
|
|
|
|
|
|
|
type NavigationProp = NativeStackNavigationProp<ProfileStackParamList, 'Profile'>;
|
|
|
|
|
|
type HomeNavigationProp = NativeStackNavigationProp<HomeStackParamList>;
|
|
|
|
|
|
type RootNavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
|
|
|
|
|
|
|
|
|
|
|
const TABS = ['帖子', '收藏'];
|
|
|
|
|
|
const TAB_ICONS = ['file-document-outline', 'bookmark-outline'];
|
|
|
|
|
|
|
|
|
|
|
|
export const ProfileScreen: React.FC = () => {
|
|
|
|
|
|
const navigation = useNavigation<NavigationProp>();
|
|
|
|
|
|
const insets = useSafeAreaInsets();
|
2026-03-16 17:47:10 +08:00
|
|
|
|
// 在大屏幕模式下不使用 Bottom Tab Navigator,所以 try-catch 处理
|
|
|
|
|
|
let tabBarHeight = 0;
|
|
|
|
|
|
try {
|
|
|
|
|
|
tabBarHeight = useBottomTabBarHeight();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 大屏幕模式下不在 Bottom Tab Navigator 中,会抛出错误,忽略即可
|
|
|
|
|
|
tabBarHeight = 0;
|
|
|
|
|
|
}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const homeNavigation = useNavigation<HomeNavigationProp>();
|
|
|
|
|
|
// 使用 any 类型来访问根导航
|
|
|
|
|
|
const rootNavigation = useNavigation<RootNavigationProp>();
|
|
|
|
|
|
const { currentUser, updateUser, fetchCurrentUser } = useAuthStore();
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
const { followUser, unfollowUser, posts: storePosts } = useUserStore();
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
// 响应式布局
|
|
|
|
|
|
const { isDesktop, isTablet, width } = useResponsive();
|
|
|
|
|
|
|
|
|
|
|
|
// 页面滚动底部安全间距,避免内容被底部 TabBar 遮挡
|
2026-03-21 03:22:28 +08:00
|
|
|
|
// TabBar 悬浮:高度 64 + 浮动间距 12*2 = 88
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const scrollBottomInset = useMemo(() => {
|
|
|
|
|
|
if (isDesktop) return spacing.lg;
|
2026-03-21 03:22:28 +08:00
|
|
|
|
const FLOATING_TAB_BAR_HEIGHT = 64 + 24; // TabBar高度 + 上下浮动间距
|
|
|
|
|
|
return FLOATING_TAB_BAR_HEIGHT + insets.bottom + spacing.md;
|
|
|
|
|
|
}, [isDesktop, insets.bottom]);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
const [activeTab, setActiveTab] = useState(0);
|
|
|
|
|
|
const [posts, setPosts] = useState<Post[]>([]);
|
|
|
|
|
|
const [favorites, setFavorites] = useState<Post[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
|
|
const scrollY = new Animated.Value(0);
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到关注列表
|
|
|
|
|
|
const handleFollowingPress = useCallback(() => {
|
|
|
|
|
|
if (currentUser) {
|
|
|
|
|
|
(rootNavigation as any).navigate('FollowList', { userId: currentUser.id, type: 'following' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [currentUser, rootNavigation]);
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到粉丝列表
|
|
|
|
|
|
const handleFollowersPress = useCallback(() => {
|
|
|
|
|
|
if (currentUser) {
|
|
|
|
|
|
(rootNavigation as any).navigate('FollowList', { userId: currentUser.id, type: 'followers' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [currentUser, rootNavigation]);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载用户帖子
|
|
|
|
|
|
const loadUserPosts = useCallback(async () => {
|
|
|
|
|
|
if (currentUser) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await postService.getUserPosts(currentUser.id);
|
|
|
|
|
|
setPosts(response.list);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户帖子失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}, [currentUser]);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载用户收藏
|
|
|
|
|
|
const loadUserFavorites = useCallback(async () => {
|
|
|
|
|
|
if (currentUser) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await postService.getUserFavorites(currentUser.id);
|
|
|
|
|
|
setFavorites(response.list);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户收藏失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}, [currentUser]);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听 tab 切换,只在数据为空时加载对应数据
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
if (activeTab === 0 && posts.length === 0) {
|
|
|
|
|
|
loadUserPosts();
|
|
|
|
|
|
} else if (activeTab === 1 && favorites.length === 0) {
|
|
|
|
|
|
loadUserFavorites();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [activeTab, loadUserPosts, loadUserFavorites, posts.length, favorites.length]);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始加载
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
loadUserPosts();
|
|
|
|
|
|
}, [loadUserPosts]);
|
|
|
|
|
|
|
|
|
|
|
|
// 同步 store 中的帖子状态到本地(用于点赞、收藏等状态更新)
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
// 同步帖子列表状态
|
|
|
|
|
|
if (posts.length > 0) {
|
|
|
|
|
|
let hasChanges = false;
|
|
|
|
|
|
const updatedPosts = posts.map(localPost => {
|
|
|
|
|
|
const storePost = storePosts.find(sp => sp.id === localPost.id);
|
|
|
|
|
|
if (storePost && (
|
|
|
|
|
|
storePost.is_liked !== localPost.is_liked ||
|
|
|
|
|
|
storePost.is_favorited !== localPost.is_favorited ||
|
|
|
|
|
|
storePost.likes_count !== localPost.likes_count ||
|
|
|
|
|
|
storePost.favorites_count !== localPost.favorites_count
|
|
|
|
|
|
)) {
|
|
|
|
|
|
hasChanges = true;
|
|
|
|
|
|
return {
|
|
|
|
|
|
...localPost,
|
|
|
|
|
|
is_liked: storePost.is_liked,
|
|
|
|
|
|
is_favorited: storePost.is_favorited,
|
|
|
|
|
|
likes_count: storePost.likes_count,
|
|
|
|
|
|
favorites_count: storePost.favorites_count,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return localPost;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (hasChanges) {
|
|
|
|
|
|
setPosts(updatedPosts);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 同步收藏列表状态
|
|
|
|
|
|
if (favorites.length > 0) {
|
|
|
|
|
|
let hasChanges = false;
|
|
|
|
|
|
const updatedFavorites = favorites.map(localPost => {
|
|
|
|
|
|
const storePost = storePosts.find(sp => sp.id === localPost.id);
|
|
|
|
|
|
if (storePost && (
|
|
|
|
|
|
storePost.is_liked !== localPost.is_liked ||
|
|
|
|
|
|
storePost.is_favorited !== localPost.is_favorited ||
|
|
|
|
|
|
storePost.likes_count !== localPost.likes_count ||
|
|
|
|
|
|
storePost.favorites_count !== localPost.favorites_count
|
|
|
|
|
|
)) {
|
|
|
|
|
|
hasChanges = true;
|
|
|
|
|
|
return {
|
|
|
|
|
|
...localPost,
|
|
|
|
|
|
is_liked: storePost.is_liked,
|
|
|
|
|
|
is_favorited: storePost.is_favorited,
|
|
|
|
|
|
likes_count: storePost.likes_count,
|
|
|
|
|
|
favorites_count: storePost.favorites_count,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return localPost;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (hasChanges) {
|
|
|
|
|
|
setFavorites(updatedFavorites);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [storePosts]);
|
|
|
|
|
|
|
|
|
|
|
|
// 下拉刷新
|
|
|
|
|
|
const onRefresh = useCallback(async () => {
|
|
|
|
|
|
setRefreshing(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 刷新用户信息
|
|
|
|
|
|
await fetchCurrentUser();
|
|
|
|
|
|
// 刷新帖子列表
|
|
|
|
|
|
await loadUserPosts();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('刷新失败:', error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [fetchCurrentUser, loadUserPosts]);
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到设置页
|
|
|
|
|
|
const handleSettings = useCallback(() => {
|
|
|
|
|
|
navigation.navigate('Settings');
|
|
|
|
|
|
}, [navigation]);
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到编辑资料页
|
|
|
|
|
|
const handleEditProfile = useCallback(() => {
|
|
|
|
|
|
navigation.navigate('EditProfile');
|
|
|
|
|
|
}, [navigation]);
|
|
|
|
|
|
|
|
|
|
|
|
// 关注/取消关注
|
|
|
|
|
|
const handleFollow = useCallback(() => {
|
|
|
|
|
|
if (!currentUser) return;
|
|
|
|
|
|
if (currentUser.is_following) {
|
|
|
|
|
|
unfollowUser(currentUser.id);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
followUser(currentUser.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [currentUser, unfollowUser, followUser]);
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到帖子详情
|
|
|
|
|
|
const handlePostPress = useCallback((postId: string, scrollToComments: boolean = false) => {
|
|
|
|
|
|
homeNavigation.navigate('PostDetail', { postId, scrollToComments });
|
|
|
|
|
|
}, [homeNavigation]);
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到用户主页(当前用户)
|
|
|
|
|
|
const handleUserPress = useCallback((userId: string) => {
|
|
|
|
|
|
// 个人主页点击自己的头像不跳转
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
// 删除帖子
|
|
|
|
|
|
const handleDeletePost = useCallback(async (postId: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const success = await postService.deletePost(postId);
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
// 从帖子列表中移除
|
|
|
|
|
|
setPosts(prev => prev.filter(p => p.id !== postId));
|
|
|
|
|
|
// 也从收藏列表中移除(如果存在)
|
|
|
|
|
|
setFavorites(prev => prev.filter(p => p.id !== postId));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('删除帖子失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除帖子失败:', error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染内容
|
|
|
|
|
|
const renderContent = useCallback(() => {
|
|
|
|
|
|
if (loading) return <Loading />;
|
|
|
|
|
|
|
|
|
|
|
|
if (activeTab === 0) {
|
|
|
|
|
|
// 帖子
|
|
|
|
|
|
if (posts.length === 0) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<EmptyState
|
|
|
|
|
|
title="还没有帖子"
|
|
|
|
|
|
description="分享你的想法,发布第一条帖子吧"
|
|
|
|
|
|
icon="file-document-edit-outline"
|
|
|
|
|
|
variant="modern"
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View style={styles.postsContainer}>
|
|
|
|
|
|
{posts.map((post, index) => {
|
|
|
|
|
|
const isPostAuthor = currentUser?.id === post.author?.id;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View key={post.id} style={[
|
|
|
|
|
|
styles.postWrapper,
|
|
|
|
|
|
index === posts.length - 1 && styles.lastPost,
|
|
|
|
|
|
]}>
|
|
|
|
|
|
<PostCard
|
|
|
|
|
|
post={post}
|
|
|
|
|
|
onPress={() => handlePostPress(post.id)}
|
|
|
|
|
|
onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}}
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
onLike={() => post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onComment={() => handlePostPress(post.id, true)}
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
onBookmark={() => post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onShare={() => {}}
|
|
|
|
|
|
onDelete={() => handleDeletePost(post.id)}
|
|
|
|
|
|
isPostAuthor={isPostAuthor}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (activeTab === 1) {
|
|
|
|
|
|
// 收藏
|
|
|
|
|
|
if (favorites.length === 0) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<EmptyState
|
|
|
|
|
|
title="还没有收藏"
|
|
|
|
|
|
description="发现喜欢的内容,点击收藏按钮保存"
|
|
|
|
|
|
icon="bookmark-heart-outline"
|
|
|
|
|
|
variant="modern"
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View style={styles.postsContainer}>
|
|
|
|
|
|
{favorites.map((post, index) => {
|
|
|
|
|
|
const isPostAuthor = currentUser?.id === post.author?.id;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View key={post.id} style={[
|
|
|
|
|
|
styles.postWrapper,
|
|
|
|
|
|
index === favorites.length - 1 && styles.lastPost,
|
|
|
|
|
|
]}>
|
|
|
|
|
|
<PostCard
|
|
|
|
|
|
post={post}
|
|
|
|
|
|
onPress={() => handlePostPress(post.id)}
|
|
|
|
|
|
onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}}
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
onLike={() => post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onComment={() => handlePostPress(post.id, true)}
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
onBookmark={() => post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onShare={() => {}}
|
|
|
|
|
|
onDelete={() => handleDeletePost(post.id)}
|
|
|
|
|
|
isPostAuthor={isPostAuthor}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
}, [loading, activeTab, posts, favorites, currentUser?.id, handlePostPress, handleUserPress, handleDeletePost]);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
// 渲染用户信息头部 - 不随 tab 变化,使用 useMemo 缓存
|
|
|
|
|
|
const renderUserHeader = useMemo(() => (
|
|
|
|
|
|
<UserProfileHeader
|
|
|
|
|
|
user={currentUser!}
|
|
|
|
|
|
isCurrentUser={true}
|
|
|
|
|
|
onFollow={handleFollow}
|
|
|
|
|
|
onSettings={handleSettings}
|
|
|
|
|
|
onEditProfile={handleEditProfile}
|
|
|
|
|
|
onFollowingPress={handleFollowingPress}
|
|
|
|
|
|
onFollowersPress={handleFollowersPress}
|
|
|
|
|
|
/>
|
|
|
|
|
|
), [currentUser, handleFollow, handleSettings, handleEditProfile, handleFollowingPress, handleFollowersPress]);
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染 TabBar 和内容
|
|
|
|
|
|
const renderTabBarAndContent = useMemo(() => (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<View style={styles.tabBarContainer}>
|
|
|
|
|
|
<TabBar
|
|
|
|
|
|
tabs={TABS}
|
|
|
|
|
|
activeIndex={activeTab}
|
|
|
|
|
|
onTabChange={setActiveTab}
|
|
|
|
|
|
variant="modern"
|
|
|
|
|
|
icons={TAB_ICONS}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<View style={styles.contentContainer}>
|
|
|
|
|
|
{renderContent()}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</>
|
|
|
|
|
|
), [activeTab, renderContent]);
|
|
|
|
|
|
|
|
|
|
|
|
if (!currentUser) {
|
|
|
|
|
|
return (
|
2026-03-18 10:58:41 +08:00
|
|
|
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
<EmptyState
|
|
|
|
|
|
title="未登录"
|
|
|
|
|
|
description="请先登录"
|
|
|
|
|
|
icon="account-off-outline"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 桌面端使用双栏布局
|
|
|
|
|
|
if (isDesktop || isTablet) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
|
|
|
|
|
<ResponsiveContainer maxWidth={1400}>
|
|
|
|
|
|
<View style={styles.desktopContainer}>
|
|
|
|
|
|
{/* 左侧:用户信息 */}
|
|
|
|
|
|
<View style={styles.desktopSidebar}>
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
|
contentContainerStyle={{ paddingBottom: scrollBottomInset }}
|
|
|
|
|
|
refreshControl={
|
|
|
|
|
|
<RefreshControl
|
|
|
|
|
|
refreshing={refreshing}
|
|
|
|
|
|
onRefresh={onRefresh}
|
|
|
|
|
|
colors={[colors.primary.main]}
|
|
|
|
|
|
tintColor={colors.primary.main}
|
|
|
|
|
|
/>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
{renderUserHeader}
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 右侧:帖子列表 */}
|
|
|
|
|
|
<View style={styles.desktopContent}>
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
|
contentContainerStyle={[styles.desktopScrollContent, { paddingBottom: scrollBottomInset }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
{renderTabBarAndContent}
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 移动端使用单栏布局
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
|
refreshControl={
|
|
|
|
|
|
<RefreshControl
|
|
|
|
|
|
refreshing={refreshing}
|
|
|
|
|
|
onRefresh={onRefresh}
|
|
|
|
|
|
colors={[colors.primary.main]}
|
|
|
|
|
|
tintColor={colors.primary.main}
|
|
|
|
|
|
/>
|
|
|
|
|
|
}
|
|
|
|
|
|
contentContainerStyle={[styles.scrollContent, { paddingBottom: scrollBottomInset }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* 用户信息头部 - 固定在顶部,不受 tab 切换影响 */}
|
|
|
|
|
|
{renderUserHeader}
|
|
|
|
|
|
|
|
|
|
|
|
{/* TabBar - 分离出来,切换 tab 不会影响上面的用户信息 */}
|
|
|
|
|
|
{renderTabBarAndContent}
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
|
|
|
|
container: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
backgroundColor: colors.background.default,
|
|
|
|
|
|
},
|
|
|
|
|
|
scrollContent: {
|
|
|
|
|
|
flexGrow: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
// 桌面端双栏布局
|
|
|
|
|
|
desktopContainer: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
gap: spacing.lg,
|
|
|
|
|
|
padding: spacing.lg,
|
|
|
|
|
|
},
|
|
|
|
|
|
desktopSidebar: {
|
|
|
|
|
|
width: 380,
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
desktopContent: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
minWidth: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
desktopScrollContent: {
|
|
|
|
|
|
flexGrow: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
tabBarContainer: {
|
|
|
|
|
|
marginTop: spacing.xs,
|
|
|
|
|
|
marginBottom: 2,
|
|
|
|
|
|
},
|
|
|
|
|
|
contentContainer: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
minHeight: 350,
|
|
|
|
|
|
paddingTop: spacing.xs,
|
|
|
|
|
|
},
|
|
|
|
|
|
postsContainer: {
|
|
|
|
|
|
paddingHorizontal: spacing.md,
|
|
|
|
|
|
paddingTop: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
postWrapper: {
|
|
|
|
|
|
marginBottom: spacing.md,
|
|
|
|
|
|
backgroundColor: colors.background.paper,
|
|
|
|
|
|
borderRadius: 16,
|
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
|
shadowColor: '#000',
|
|
|
|
|
|
shadowOffset: { width: 0, height: 2 },
|
|
|
|
|
|
shadowOpacity: 0.06,
|
|
|
|
|
|
shadowRadius: 8,
|
|
|
|
|
|
elevation: 2,
|
|
|
|
|
|
},
|
|
|
|
|
|
lastPost: {
|
|
|
|
|
|
marginBottom: spacing['2xl'],
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
export default ProfileScreen;
|