refactor(ImageGallery, HomeScreen, PostService, UserStore): streamline post type handling and improve tab navigation
- Updated ImageGallery to manage loading states more effectively and prevent unnecessary updates. - Refactored HomeScreen to remove the 'recommend' post type, simplifying the post type options. - Adjusted PostService and UserStore to eliminate references to 'recommend', ensuring consistency across the application. - Enhanced tab navigation in SimpleMobileTabNavigator to optimize rendering and manage mounted tabs more efficiently. - Improved error handling and loading states in various components for better user experience.
This commit is contained in:
@@ -86,7 +86,9 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
||||
}) => {
|
||||
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const isClosingRef = useRef(false);
|
||||
const currentIndexRef = useRef(initialIndex);
|
||||
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
||||
currentIndexRef.current = currentIndex;
|
||||
const [showControls, setShowControls] = useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
@@ -152,16 +154,19 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 图片变化时重置加载状态和缩放
|
||||
// 图片变化时重置缩放(加载态在 updateIndex / 打开弹窗时同步设置,避免晚一帧仍显示上一张)
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
resetZoom();
|
||||
}, [currentImage?.id, resetZoom]);
|
||||
|
||||
const updateIndex = useCallback(
|
||||
(newIndex: number) => {
|
||||
const clampedIndex = Math.max(0, Math.min(validImages.length - 1, newIndex));
|
||||
if (clampedIndex === currentIndexRef.current) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
setCurrentIndex(clampedIndex);
|
||||
onIndexChange?.(clampedIndex);
|
||||
},
|
||||
@@ -412,7 +417,8 @@ export const ImageGallery: React.FC<ImageGalleryProps> = ({
|
||||
contentFit="contain"
|
||||
cachePolicy="disk"
|
||||
priority="high"
|
||||
recyclingKey={currentImage.id}
|
||||
recyclingKey={currentImage.url}
|
||||
transition={null}
|
||||
allowDownscaling
|
||||
onLoadStart={() => setLoading(true)}
|
||||
onLoad={() => {
|
||||
@@ -556,11 +562,13 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 5,
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
errorContainer: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
errorText: {
|
||||
color: '#999',
|
||||
|
||||
@@ -17,8 +17,8 @@ export interface GetPostsParams {
|
||||
pageSize?: number;
|
||||
/** 游标 - 用于无限滚动加载 */
|
||||
cursor?: string;
|
||||
/** 帖子类型筛选(可选):recommend, follow, hot, latest */
|
||||
post_type?: 'recommend' | 'follow' | 'hot' | 'latest';
|
||||
/** 帖子类型筛选(可选):follow, hot, latest */
|
||||
post_type?: 'follow' | 'hot' | 'latest';
|
||||
/** 社区ID过滤 */
|
||||
communityId?: string;
|
||||
/** 作者ID过滤 */
|
||||
|
||||
@@ -134,7 +134,7 @@ const prefetchService = new PrefetchService();
|
||||
* 预取帖子数据
|
||||
* @param types 帖子类型数组
|
||||
*/
|
||||
function prefetchPosts(types: string[] = ['recommend', 'hot']): void {
|
||||
function prefetchPosts(types: string[] = ['latest', 'hot']): void {
|
||||
types.forEach((type, index) => {
|
||||
prefetchService.schedule({
|
||||
key: `posts:${type}:1`,
|
||||
@@ -230,7 +230,7 @@ function prefetchOnAppLaunch(): void {
|
||||
prefetchConversations();
|
||||
|
||||
// 中优先级:帖子列表
|
||||
prefetchPosts(['recommend']);
|
||||
prefetchPosts(['latest']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,7 +257,7 @@ function prefetchMessageScreen(): void {
|
||||
* 进入首页时预取
|
||||
*/
|
||||
function prefetchHomeScreen(): void {
|
||||
prefetchPosts(['recommend', 'hot', 'latest']);
|
||||
prefetchPosts(['latest', 'hot', 'follow']);
|
||||
}
|
||||
|
||||
// ==================== React Hook ====================
|
||||
|
||||
@@ -178,6 +178,12 @@ export function SimpleMobileTabNavigator() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const messageUnreadCount = useTotalUnreadCount();
|
||||
const [activeTab, setActiveTab] = useState<TabName>('HomeTab');
|
||||
const [mountedTabs, setMountedTabs] = useState<Record<TabName, boolean>>({
|
||||
HomeTab: true,
|
||||
MessageTab: false,
|
||||
ScheduleTab: false,
|
||||
ProfileTab: false,
|
||||
});
|
||||
|
||||
// 初始化 MessageManager
|
||||
useEffect(() => {
|
||||
@@ -186,12 +192,21 @@ export function SimpleMobileTabNavigator() {
|
||||
|
||||
// 处理 Tab 切换
|
||||
const handleTabPress = useCallback((tabName: TabName) => {
|
||||
setMountedTabs((prev) => {
|
||||
if (prev[tabName]) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
[tabName]: true,
|
||||
};
|
||||
});
|
||||
setActiveTab(tabName);
|
||||
}, []);
|
||||
|
||||
// 渲染当前 Tab 的内容
|
||||
const renderTabContent = () => {
|
||||
switch (activeTab) {
|
||||
// 根据 Tab 名称渲染对应内容
|
||||
const renderTabView = (tabName: TabName) => {
|
||||
switch (tabName) {
|
||||
case 'HomeTab':
|
||||
return <HomeScreen />;
|
||||
case 'MessageTab':
|
||||
@@ -276,7 +291,24 @@ export function SimpleMobileTabNavigator() {
|
||||
<View style={styles.container}>
|
||||
{/* 内容区域 */}
|
||||
<View style={styles.content}>
|
||||
{renderTabContent()}
|
||||
{(Object.keys(mountedTabs) as TabName[]).map((tabName) => {
|
||||
if (!mountedTabs[tabName]) {
|
||||
return null;
|
||||
}
|
||||
const isActive = activeTab === tabName;
|
||||
return (
|
||||
<View
|
||||
key={tabName}
|
||||
style={[
|
||||
styles.tabScreen,
|
||||
!isActive && styles.tabScreenHidden,
|
||||
]}
|
||||
pointerEvents={isActive ? 'auto' : 'none'}
|
||||
>
|
||||
{renderTabView(tabName)}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* Tab Bar */}
|
||||
@@ -334,6 +366,13 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
position: 'relative',
|
||||
},
|
||||
tabScreen: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
},
|
||||
tabScreenHidden: {
|
||||
display: 'none',
|
||||
},
|
||||
bottomSpacer: {
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
@@ -41,8 +41,8 @@ import { navigationService } from '../../infrastructure/navigation/navigationSer
|
||||
|
||||
type NavigationProp = NativeStackNavigationProp<HomeStackParamList, 'Home'> & NativeStackNavigationProp<RootStackParamList>;
|
||||
|
||||
const TABS = ['推荐', '关注', '热门', '最新'];
|
||||
const TAB_ICONS = ['compass-outline', 'account-heart-outline', 'fire', 'clock-outline'];
|
||||
const TABS = ['最新', '关注', '热门'];
|
||||
const TAB_ICONS = ['clock-outline', 'account-heart-outline', 'fire'];
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
const SWIPE_TRANSLATION_THRESHOLD = 40;
|
||||
const SWIPE_COOLDOWN_MS = 300;
|
||||
@@ -51,7 +51,7 @@ const MOBILE_TAB_FLOATING_MARGIN = 12;
|
||||
const MOBILE_FAB_GAP = 12;
|
||||
|
||||
type ViewMode = 'list' | 'grid';
|
||||
type PostType = 'recommend' | 'follow' | 'hot' | 'latest';
|
||||
type PostType = 'follow' | 'hot' | 'latest';
|
||||
|
||||
export const HomeScreen: React.FC = () => {
|
||||
const navigation = useNavigation<NavigationProp>();
|
||||
@@ -106,11 +106,10 @@ export const HomeScreen: React.FC = () => {
|
||||
// 获取当前 tab 对应的帖子类型
|
||||
const getPostType = useCallback((): PostType => {
|
||||
switch (activeIndex) {
|
||||
case 0: return 'recommend';
|
||||
case 0: return 'latest';
|
||||
case 1: return 'follow';
|
||||
case 2: return 'hot';
|
||||
case 3: return 'latest';
|
||||
default: return 'recommend';
|
||||
default: return 'latest';
|
||||
}
|
||||
}, [activeIndex]);
|
||||
|
||||
|
||||
@@ -63,11 +63,6 @@ class PostService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// 获取推荐帖子
|
||||
async getRecommendedPosts(page = 1, pageSize = 20): Promise<PaginatedData<Post>> {
|
||||
return this.getPosts(page, pageSize, 'recommend');
|
||||
}
|
||||
|
||||
// 获取关注帖子
|
||||
async getFollowingPosts(page = 1, pageSize = 20): Promise<PaginatedData<Post>> {
|
||||
return this.getPosts(page, pageSize, 'follow');
|
||||
@@ -282,7 +277,7 @@ class PostService {
|
||||
/**
|
||||
* 获取帖子列表(游标分页)
|
||||
* GET /api/v1/posts
|
||||
* @param params 游标分页请求参数(包含 post_type 可选:recommend, follow, hot, latest)
|
||||
* @param params 游标分页请求参数(包含 post_type 可选:follow, hot, latest)
|
||||
*/
|
||||
async getPostsCursor(
|
||||
params: CursorPaginationRequest = {}
|
||||
|
||||
@@ -63,7 +63,7 @@ class PostManager extends CacheBus<PostManagerSnapshot, PostManagerEvent> {
|
||||
}
|
||||
|
||||
async getPosts(
|
||||
type = 'recommend',
|
||||
type = 'hot',
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
forceRefresh = false
|
||||
|
||||
@@ -34,7 +34,7 @@ interface UserState {
|
||||
// 操作
|
||||
fetchUser: (userId: string) => Promise<User | undefined>;
|
||||
fetchUserPosts: (userId: string, page?: number) => Promise<Post[]>;
|
||||
fetchPosts: (type: 'recommend' | 'follow' | 'hot' | 'latest', page?: number) => Promise<PaginatedData<Post>>;
|
||||
fetchPosts: (type: 'follow' | 'hot' | 'latest', page?: number) => Promise<PaginatedData<Post>>;
|
||||
fetchNotifications: (type?: string, page?: number) => Promise<Notification[]>;
|
||||
markNotificationAsRead: (notificationId: string) => Promise<void>;
|
||||
markAllNotificationsAsRead: () => Promise<void>;
|
||||
@@ -108,16 +108,13 @@ export const useUserStore = create<UserState>((set, get) => {
|
||||
},
|
||||
|
||||
// 获取帖子列表(首页)
|
||||
fetchPosts: async (type: 'recommend' | 'follow' | 'hot' | 'latest', page = 1) => {
|
||||
fetchPosts: async (type: 'follow' | 'hot' | 'latest', page = 1) => {
|
||||
set({ isLoadingPosts: true });
|
||||
|
||||
try {
|
||||
let response;
|
||||
|
||||
switch (type) {
|
||||
case 'recommend':
|
||||
response = await postService.getRecommendedPosts(page);
|
||||
break;
|
||||
case 'follow':
|
||||
response = await postService.getFollowingPosts(page);
|
||||
break;
|
||||
@@ -332,7 +329,7 @@ export const useUserStore = create<UserState>((set, get) => {
|
||||
// 刷新所有数据
|
||||
refreshAll: async () => {
|
||||
await Promise.all([
|
||||
get().fetchPosts('recommend', 1),
|
||||
get().fetchPosts('latest', 1),
|
||||
get().fetchNotificationBadge(),
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -482,8 +482,8 @@ export interface CursorPaginationRequest {
|
||||
direction?: 'forward' | 'backward';
|
||||
/** 每页数量(默认 20,最大 100) */
|
||||
page_size?: number;
|
||||
/** 帖子类型筛选(可选):recommend, follow, hot, latest */
|
||||
post_type?: 'recommend' | 'follow' | 'hot' | 'latest';
|
||||
/** 帖子类型筛选(可选):follow, hot, latest */
|
||||
post_type?: 'follow' | 'hot' | 'latest';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user