refactor(Post, PostMapper, PostRepository, CreatePostScreen, HomeScreen): update communityId to channelId for improved clarity
Some checks failed
Frontend CI / build-and-push-web (push) Successful in 2m48s
Frontend CI / ota-android (push) Successful in 11m3s
Frontend CI / build-android-apk (push) Has been cancelled

- 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:
lafay
2026-03-24 22:27:33 +08:00
parent b49cc0f3bd
commit 126e204592
15 changed files with 439 additions and 260 deletions

View File

@@ -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,
},