refactor(Post, PostMapper, PostRepository, CreatePostScreen, HomeScreen): update communityId to channelId for improved clarity
- Renamed communityId to channelId across Post entity, PostMapper, and PostRepository for consistency and clarity. - Updated CreatePostScreen to include channel selection functionality, enhancing user experience when creating posts. - Adjusted HomeScreen to support filtering posts by channel, improving content organization. - Refactored related interfaces and services to align with the new channelId terminology, ensuring a cohesive codebase.
This commit is contained in:
@@ -9,6 +9,9 @@ import {
|
||||
View,
|
||||
FlatList,
|
||||
ScrollView,
|
||||
LayoutAnimation,
|
||||
Platform,
|
||||
UIManager,
|
||||
StyleSheet,
|
||||
RefreshControl,
|
||||
StatusBar,
|
||||
@@ -26,7 +29,7 @@ import { colors, spacing, borderRadius, shadows } from '../../theme';
|
||||
import { Post } from '../../types';
|
||||
import { useUserStore } from '../../stores';
|
||||
import { useCurrentUser } from '../../stores/authStore';
|
||||
import { postService } from '../../services';
|
||||
import { channelService, postService } from '../../services';
|
||||
import { PostCard, TabBar, SearchBar } from '../../components/business';
|
||||
import type { PostCardAction } from '../../components/business/PostCard';
|
||||
import { Loading, EmptyState, Text, ImageGallery, ImageGridItem, ResponsiveGrid } from '../../components/common';
|
||||
@@ -37,17 +40,19 @@ import { SearchScreen } from './SearchScreen';
|
||||
import { CreatePostScreen } from '../create/CreatePostScreen';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
const TABS = ['最新', '关注', '热门'];
|
||||
const TAB_ICONS = ['clock-outline', 'account-heart-outline', 'fire'];
|
||||
const TABS = ['关注', '最新', '热门'];
|
||||
const TAB_ICONS = ['account-heart-outline', 'clock-outline', 'fire'];
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
const SWIPE_TRANSLATION_THRESHOLD = 40;
|
||||
const SWIPE_COOLDOWN_MS = 300;
|
||||
const MOBILE_TAB_BAR_HEIGHT = 64;
|
||||
const MOBILE_TAB_FLOATING_MARGIN = 12;
|
||||
const MOBILE_FAB_GAP = 12;
|
||||
const CAPSULE_HIDE_THRESHOLD = 24;
|
||||
|
||||
type ViewMode = 'list' | 'grid';
|
||||
type PostType = 'follow' | 'hot' | 'latest';
|
||||
type LatestCapsule = { id: string; name: string };
|
||||
|
||||
export const HomeScreen: React.FC = () => {
|
||||
const router = useRouter();
|
||||
@@ -68,8 +73,11 @@ export const HomeScreen: React.FC = () => {
|
||||
const responsiveGap = useResponsiveSpacing({ xs: 4, sm: 6, md: 8, lg: 12, xl: 16 });
|
||||
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [activeIndex, setActiveIndex] = useState(1);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('list');
|
||||
const [latestCapsules, setLatestCapsules] = useState<LatestCapsule[]>([{ id: '', name: '全部' }]);
|
||||
const [activeCapsuleId, setActiveCapsuleId] = useState('');
|
||||
const [showLatestCapsules, setShowLatestCapsules] = useState(true);
|
||||
|
||||
// 图片查看器状态
|
||||
const [showImageViewer, setShowImageViewer] = useState(false);
|
||||
@@ -88,8 +96,13 @@ export const HomeScreen: React.FC = () => {
|
||||
|
||||
// 网格模式滚动位置检测
|
||||
const gridScrollYRef = useRef(0);
|
||||
const listScrollYRef = useRef(0);
|
||||
const isLoadingMoreRef = useRef(false);
|
||||
|
||||
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
|
||||
// 构建一个以 postId 为 key 的 map,用于快速查找
|
||||
const postsMap = useMemo(() => {
|
||||
const map = new Map<string, Post>();
|
||||
@@ -102,15 +115,36 @@ export const HomeScreen: React.FC = () => {
|
||||
// 获取当前 tab 对应的帖子类型
|
||||
const getPostType = useCallback((): PostType => {
|
||||
switch (activeIndex) {
|
||||
case 0: return 'latest';
|
||||
case 1: return 'follow';
|
||||
case 0: return 'follow';
|
||||
case 1: return 'latest';
|
||||
case 2: return 'hot';
|
||||
default: return 'latest';
|
||||
}
|
||||
}, [activeIndex]);
|
||||
|
||||
const isLatestTab = activeIndex === 1;
|
||||
const currentChannelId = isLatestTab && activeCapsuleId ? activeCapsuleId : undefined;
|
||||
|
||||
const updateCapsuleVisibilityByScroll = useCallback((nextY: number, previousY: number) => {
|
||||
if (!isLatestTab) return;
|
||||
const delta = nextY - previousY;
|
||||
const isScrollingDown = delta > CAPSULE_HIDE_THRESHOLD;
|
||||
const isScrollingUp = delta < -CAPSULE_HIDE_THRESHOLD;
|
||||
|
||||
if (isScrollingDown && showLatestCapsules) {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setShowLatestCapsules(false);
|
||||
} else if (isScrollingUp && !showLatestCapsules) {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setShowLatestCapsules(true);
|
||||
}
|
||||
}, [isLatestTab, showLatestCapsules]);
|
||||
|
||||
// 使用差异更新 Hook 获取帖子列表
|
||||
const listKey = useMemo(() => `home_${getPostType()}`, [getPostType]);
|
||||
const listKey = useMemo(
|
||||
() => `home_${getPostType()}_${currentChannelId || 'all'}`,
|
||||
[getPostType, currentChannelId]
|
||||
);
|
||||
|
||||
const {
|
||||
posts,
|
||||
@@ -162,27 +196,34 @@ export const HomeScreen: React.FC = () => {
|
||||
if (scrollY + visibleHeight >= contentHeight - 200) {
|
||||
loadMore();
|
||||
}
|
||||
}, [loadMore]);
|
||||
updateCapsuleVisibilityByScroll(scrollY, gridScrollYRef.current);
|
||||
gridScrollYRef.current = scrollY;
|
||||
}, [loadMore, updateCapsuleVisibilityByScroll]);
|
||||
|
||||
// 刷新方法 - 先获取正确类型的帖子,再刷新
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
// 先获取正确类型的帖子
|
||||
await processPostUseCase.fetchPosts(
|
||||
{ page: 1, pageSize: DEFAULT_PAGE_SIZE, post_type: getPostType() },
|
||||
{
|
||||
page: 1,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
post_type: getPostType(),
|
||||
channelId: currentChannelId,
|
||||
},
|
||||
listKey
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('刷新失败:', err);
|
||||
}
|
||||
}, [listKey, getPostType]);
|
||||
}, [listKey, getPostType, currentChannelId]);
|
||||
|
||||
// Tab 切换时刷新数据并重置列表
|
||||
useEffect(() => {
|
||||
reset();
|
||||
refresh();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeIndex]);
|
||||
}, [activeIndex, currentChannelId]);
|
||||
|
||||
// 将获取到的原始帖子与 store 中的状态合并
|
||||
// 这样 UI 始终显示的是 store 中的最新状态(包括点赞、收藏等)
|
||||
@@ -262,6 +303,24 @@ export const HomeScreen: React.FC = () => {
|
||||
setActiveIndex(nextIndex);
|
||||
}, [activeIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowLatestCapsules(true);
|
||||
listScrollYRef.current = 0;
|
||||
gridScrollYRef.current = 0;
|
||||
}, [activeIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadChannels = async () => {
|
||||
const list = await channelService.list();
|
||||
const capsules: LatestCapsule[] = [
|
||||
{ id: '', name: '全部' },
|
||||
...list.map(item => ({ id: item.id, name: item.name })),
|
||||
];
|
||||
setLatestCapsules(capsules);
|
||||
};
|
||||
loadChannels();
|
||||
}, []);
|
||||
|
||||
const handleSwipeTabChange = useCallback((translationX: number) => {
|
||||
setActiveIndex(prev => (
|
||||
translationX < 0
|
||||
@@ -421,6 +480,40 @@ export const HomeScreen: React.FC = () => {
|
||||
setShowCreatePost(true);
|
||||
};
|
||||
|
||||
const renderLatestCapsules = () => {
|
||||
if (!isLatestTab || !showLatestCapsules) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.capsuleWrapper, { paddingHorizontal: responsivePadding }]}>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.capsuleList}>
|
||||
{latestCapsules.map((item) => {
|
||||
const isActive = item.id === activeCapsuleId;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={item.id || 'all'}
|
||||
activeOpacity={0.85}
|
||||
onPress={() => setActiveCapsuleId(item.id)}
|
||||
style={styles.capsuleItem}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
isActive
|
||||
? { ...styles.capsuleText, ...styles.capsuleTextActive }
|
||||
: styles.capsuleText
|
||||
}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染帖子卡片(列表模式)
|
||||
const keyExtractor = useCallback((item: Post) => item.id, []);
|
||||
|
||||
@@ -559,6 +652,7 @@ export const HomeScreen: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
{renderLatestCapsules()}
|
||||
<View style={styles.waterfallColumnsRow}>
|
||||
{distributePostsToColumns.map((column, index) => renderWaterfallColumn(column, index))}
|
||||
</View>
|
||||
@@ -621,6 +715,7 @@ export const HomeScreen: React.FC = () => {
|
||||
data={displayPosts}
|
||||
renderItem={renderPostList}
|
||||
keyExtractor={keyExtractor}
|
||||
ListHeaderComponent={renderLatestCapsules()}
|
||||
contentContainerStyle={[
|
||||
styles.listContent,
|
||||
{
|
||||
@@ -640,6 +735,12 @@ export const HomeScreen: React.FC = () => {
|
||||
}
|
||||
onEndReached={loadMore}
|
||||
onEndReachedThreshold={0.3}
|
||||
onScroll={(event) => {
|
||||
const nextY = event.nativeEvent.contentOffset.y;
|
||||
updateCapsuleVisibilityByScroll(nextY, listScrollYRef.current);
|
||||
listScrollYRef.current = nextY;
|
||||
}}
|
||||
scrollEventThrottle={16}
|
||||
ListEmptyComponent={renderEmpty}
|
||||
ListFooterComponent={isLoadingMore ? <Loading size="sm" /> : null}
|
||||
initialNumToRender={8}
|
||||
@@ -793,6 +894,27 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
capsuleWrapper: {
|
||||
paddingBottom: spacing.sm,
|
||||
backgroundColor: colors.background.paper,
|
||||
},
|
||||
capsuleList: {
|
||||
gap: spacing.xs,
|
||||
paddingRight: spacing.lg,
|
||||
},
|
||||
capsuleItem: {
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: spacing.xs,
|
||||
borderRadius: 999,
|
||||
},
|
||||
capsuleText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '600',
|
||||
color: colors.text.secondary,
|
||||
},
|
||||
capsuleTextActive: {
|
||||
color: colors.primary.main,
|
||||
},
|
||||
listContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user