diff --git a/package-lock.json b/package-lock.json index 586d224..507e99e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "carrot_bbs", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "carrot_bbs", - "version": "1.0.0", + "version": "1.0.1", "dependencies": { "@expo/vector-icons": "^15.1.1", "@react-native-async-storage/async-storage": "^2.2.0", diff --git a/src/components/business/PostCard.tsx b/src/components/business/PostCard.legacy.tsx similarity index 100% rename from src/components/business/PostCard.tsx rename to src/components/business/PostCard.legacy.tsx diff --git a/src/components/business/PostCard/PostCard.tsx b/src/components/business/PostCard/PostCard.tsx new file mode 100644 index 0000000..6cc61f9 --- /dev/null +++ b/src/components/business/PostCard/PostCard.tsx @@ -0,0 +1,74 @@ +/** + * PostCard 主组件 + * 帖子卡片组件 - 支持列表模式和网格模式 + * + * @example + * // 新 API 使用方式 + * handleAction(post, action)} + * variant="list" + * features="full" + * /> + */ + +import React from 'react'; +import { PostCardProps, LegacyPostCardProps } from './types'; +import PostCardList from './PostCardList'; +import PostCardGrid from './PostCardGrid'; +import { usePostCardFeatures } from './hooks/usePostCardFeatures'; +import { createLegacyAdapter, isLegacyProps } from './legacyAdapter'; + +/** + * PostCard 主组件 + * 支持新旧两种 API + */ +const PostCard: React.FC = (props) => { + // 检测是否使用旧 API + const isLegacy = isLegacyProps(props as LegacyPostCardProps); + + // 兼容层:转换旧 Props 到新 Props + const normalizedProps: PostCardProps = isLegacy + ? createLegacyAdapter(props as LegacyPostCardProps) + : (props as PostCardProps); + + const { + post, + onAction, + variant = 'list', + features: featuresConfig, + isPostAuthor = false, + isAuthor = false, + index, + style, + } = normalizedProps; + + // 解析 Features 配置 + const resolvedFeatures = usePostCardFeatures(featuresConfig); + + // 根据 variant 选择渲染模式 + if (variant === 'grid') { + return ( + + ); + } + + return ( + + ); +}; + +export default PostCard; \ No newline at end of file diff --git a/src/components/business/PostCard/PostCardGrid.tsx b/src/components/business/PostCard/PostCardGrid.tsx new file mode 100644 index 0000000..93306d2 --- /dev/null +++ b/src/components/business/PostCard/PostCardGrid.tsx @@ -0,0 +1,115 @@ +/** + * PostCardGrid 容器组件 + * 网格模式(小红书风格)的帖子卡片容器 + */ + +import React from 'react'; +import { TouchableOpacity, StyleSheet, StyleProp, ViewStyle, GestureResponderEvent } from 'react-native'; +import { colors, borderRadius } from '../../../theme'; +import { useResponsive } from '../../../hooks/useResponsive'; +import { PostCardGridProps } from './types'; +import { usePostCardActions } from './hooks/usePostCardActions'; +import { usePostGridStyles } from './hooks/usePostCardStyles'; +import { PostGridCover, PostGridInfo } from './components'; +import { convertToImageGridItems, getConsistentAspectRatio } from './utils'; + +const PostCardGrid: React.FC = ({ + post, + onAction, + features, + style, +}) => { + const { isDesktop } = useResponsive(); + const gridStyles = usePostGridStyles(); + + const { + handlePress, + handleUserPress, + handleImagePress, + } = usePostCardActions(onAction); + + // 防御性检查:确保 author 存在 + const author = post.author ?? { + id: '', + nickname: '匿名用户', + avatar: '', + username: '', + cover_url: '', + bio: '', + website: '', + location: '', + posts_count: 0, + followers_count: 0, + following_count: 0, + created_at: '', + }; + + // 获取封面图(第一张图) + const coverImage = post.images && post.images.length > 0 ? post.images[0] : null; + + // 根据帖子 ID 生成一致的宽高比 + const aspectRatio = getConsistentAspectRatio(post.id); + + // 样式计算 + const containerStyle: StyleProp = [ + styles.container, + !coverImage && styles.containerNoImage, + isDesktop && styles.containerDesktop, + ]; + + const handleContainerPress = () => { + handlePress(); + }; + + const handleImagePressLocal = () => { + if (post.images && post.images.length > 0) { + handleImagePress(convertToImageGridItems(post.images), 0); + } + }; + + const handleUserPressLocal = () => { + handleUserPress(); + }; + + return ( + + {/* 封面 */} + + + {/* 标题和信息 */} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#FFF', + overflow: 'hidden', + borderRadius: 8, + }, + containerDesktop: { + borderRadius: 12, + }, + containerNoImage: { + minHeight: 200, + justifyContent: 'space-between', + }, +}); + +export default PostCardGrid; \ No newline at end of file diff --git a/src/components/business/PostCard/components/PostImages.tsx b/src/components/business/PostCard/components/PostImages.tsx new file mode 100644 index 0000000..8a4ee81 --- /dev/null +++ b/src/components/business/PostCard/components/PostImages.tsx @@ -0,0 +1,53 @@ +/** + * PostImages 子组件 + * 显示帖子图片网格 + */ + +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { colors, spacing, borderRadius } from '../../../../theme'; +import { useResponsive } from '../../../../hooks/useResponsive'; +import { ImageGrid, ImageGridItem } from '../../../common'; +import { PostImagesProps } from '../types'; + +const PostImages: React.FC = ({ images, displayMode, onImagePress }) => { + const { isDesktop, isWideScreen } = useResponsive(); + + if (!images || images.length === 0) return null; + + // 计算图片间距和圆角 + const imageGap = isDesktop ? 12 : 8; + const imageBorderRadius = isDesktop ? borderRadius.xl : borderRadius.md; + + return ( + + ({ + id: img.id, + url: img.url, + thumbnail_url: img.thumbnail_url, + preview_url: img.preview_url, + preview_url_large: img.preview_url_large, + width: img.width, + height: img.height, + }))} + maxDisplayCount={isWideScreen ? 12 : 9} + mode="auto" + gap={imageGap} + borderRadius={imageBorderRadius} + showMoreOverlay={true} + onImagePress={onImagePress} + displayMode={displayMode} + usePreview={true} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginBottom: spacing.md, + }, +}); + +export default PostImages; \ No newline at end of file diff --git a/src/components/business/PostCard/components/index.ts b/src/components/business/PostCard/components/index.ts new file mode 100644 index 0000000..98f9fc3 --- /dev/null +++ b/src/components/business/PostCard/components/index.ts @@ -0,0 +1,12 @@ +/** + * PostCard 子组件导出 + */ + +export { default as PostHeader } from './PostHeader'; +export { default as PostTitle } from './PostTitle'; +export { default as PostContent } from './PostContent'; +export { default as PostImages } from './PostImages'; +export { default as PostActions } from './PostActions'; +export { default as PostTopComment } from './PostTopComment'; +export { default as PostGridCover } from './PostGridCover'; +export { default as PostGridInfo } from './PostGridInfo'; \ No newline at end of file diff --git a/src/components/business/PostCard/hooks/index.ts b/src/components/business/PostCard/hooks/index.ts new file mode 100644 index 0000000..0025b4e --- /dev/null +++ b/src/components/business/PostCard/hooks/index.ts @@ -0,0 +1,8 @@ +/** + * PostCard Hooks 导出 + */ + +export { usePostCardActions } from './usePostCardActions'; +export { usePostCardFeatures } from './usePostCardFeatures'; +export { usePostCardStyles, usePostGridStyles } from './usePostCardStyles'; +export type { PostCardStylesConfig, PostGridStylesConfig } from './usePostCardStyles'; diff --git a/src/components/business/PostCard/index.ts b/src/components/business/PostCard/index.ts new file mode 100644 index 0000000..b85d14e --- /dev/null +++ b/src/components/business/PostCard/index.ts @@ -0,0 +1,58 @@ +/** + * PostCard 组件模块导出 + */ + +// 主组件(默认导出) +export { default } from './PostCard'; + +// 类型导出 +export type { + PostCardActionType, + PostCardActionPayload, + PostCardAction, + PostCardOnAction, + PostCardFeatures, + PostCardFeaturesPreset, + PostCardFeaturesConfig, + PostCardProps, + PostHeaderProps, + PostTitleProps, + PostContentProps, + PostImagesProps, + PostActionsProps, + PostVotePreviewProps, + PostTopCommentProps, + PostGridCoverProps, + PostGridInfoProps, + PostCardListProps, + PostCardGridProps, + LegacyPostCardProps, + Badge, +} from './types'; + +// 预设配置导出 +export { PostCardPresets, defaultFeatures, resolveFeatures } from './presets'; + +// Hooks 导出 +export { + usePostCardActions, + usePostCardFeatures, + usePostCardStyles, + usePostGridStyles, +} from './hooks'; +export type { PostCardStylesConfig, PostGridStylesConfig } from './hooks'; + +// 子组件导出 +export { + PostHeader, + PostTitle, + PostContent, + PostImages, + PostActions, + PostTopComment, + PostGridCover, + PostGridInfo, +} from './components'; + +// 兼容层导出(用于迁移帮助) +export { isLegacyProps, createLegacyAdapter } from './legacyAdapter'; \ No newline at end of file diff --git a/src/components/business/PostCard/types.ts b/src/components/business/PostCard/types.ts new file mode 100644 index 0000000..510edb0 --- /dev/null +++ b/src/components/business/PostCard/types.ts @@ -0,0 +1,285 @@ +/** + * PostCard 组件类型定义 + * 统一的帖子卡片组件类型系统 + */ + +import { StyleProp, ViewStyle } from 'react-native'; +import { Post, PostImageDTO, UserDTO, CommentDTO } from '../../../types'; +import { ImageGridItem } from '../../common'; + +// ==================== Action 类型定义 ==================== + +/** + * PostCard 支持的操作类型 + */ +export type PostCardActionType = + | 'press' // 点击帖子主体 + | 'userPress' // 点击用户头像/名称 + | 'like' // 点赞 + | 'unlike' // 取消点赞 + | 'comment' // 评论 + | 'bookmark' // 收藏 + | 'unbookmark' // 取消收藏 + | 'share' // 分享 + | 'imagePress' // 点击图片 + | 'delete'; // 删除 + +/** + * Action payload 类型 + */ +export interface PostCardActionPayload { + images?: ImageGridItem[]; + imageIndex?: number; + [key: string]: unknown; +} + +/** + * 统一的 Action 对象 + */ +export interface PostCardAction { + type: PostCardActionType; + payload?: PostCardActionPayload; +} + +/** + * 统一的回调函数类型 + */ +export type PostCardOnAction = (action: PostCardAction) => void; + +// ==================== Features 配置类型 ==================== + +/** + * 显示特性配置 + */ +export interface PostCardFeatures { + /** 显示用户信息 */ + showAuthor?: boolean; + /** 显示标题 */ + showTitle?: boolean; + /** 显示内容 */ + showContent?: boolean; + /** 显示图片 */ + showImages?: boolean; + /** 显示投票预览 */ + showVote?: boolean; + /** 显示热门评论 */ + showTopComment?: boolean; + /** 显示操作栏 */ + showActions?: boolean; + /** 显示浏览数 */ + showViews?: boolean; + /** 显示分享按钮 */ + showShare?: boolean; + /** 显示收藏按钮 */ + showBookmark?: boolean; + /** 显示删除按钮(需配合权限判断) */ + showDelete?: boolean; + /** 显示置顶标签 */ + showPinned?: boolean; + /** 显示徽章(楼主/管理员) */ + showBadges?: boolean; +} + +/** + * Features 预设名称 + */ +export type PostCardFeaturesPreset = 'full' | 'compact' | 'grid'; + +/** + * Features 配置(可以是预设名称或自定义对象) + */ +export type PostCardFeaturesConfig = PostCardFeaturesPreset | PostCardFeatures; + +// ==================== 主组件 Props ==================== + +/** + * PostCard 主组件 Props + */ +export interface PostCardProps { + /** 帖子数据 */ + post: Post; + + /** 统一事件回调 */ + onAction: PostCardOnAction; + + /** 显示模式:列表模式 | 网格模式 */ + variant?: 'list' | 'grid'; + + /** 预设配置名称,或自定义配置对象 */ + features?: PostCardFeaturesConfig; + + /** 当前用户是否为帖子作者(用于显示删除按钮) */ + isPostAuthor?: boolean; + + /** 当前用户是否为楼主(在帖子详情页显示"楼主"徽章) */ + isAuthor?: boolean; + + /** 索引(用于虚拟化列表优化) */ + index?: number; + + /** 自定义样式 */ + style?: StyleProp; +} + +// ==================== 子组件 Props ==================== + +/** + * PostHeader 子组件 Props + */ +export interface PostHeaderProps { + author: UserDTO; + createdAt: string; + editedAt?: string; + isPinned?: boolean; + badges?: Array<{ type: 'author' | 'admin'; label: string }>; + onDelete?: () => void; + onUserPress: () => void; + showDelete?: boolean; + showPinned?: boolean; + showBadges?: boolean; + isDeleting?: boolean; +} + +/** + * PostTitle 子组件 Props + */ +export interface PostTitleProps { + title?: string; + numberOfLines?: number; +} + +/** + * PostContent 子组件 Props + */ +export interface PostContentProps { + content?: string; + compact?: boolean; + maxLines?: number; + isExpanded?: boolean; + onToggleExpand?: () => void; +} + +/** + * PostImages 子组件 Props + */ +export interface PostImagesProps { + images: PostImageDTO[]; + displayMode: 'list' | 'grid'; + onImagePress: (images: ImageGridItem[], index: number) => void; +} + +/** + * PostActions 子组件 Props + */ +export interface PostActionsProps { + viewsCount: number; + likesCount: number; + commentsCount: number; + isLiked: boolean; + isFavorited: boolean; + onLike: () => void; + onComment: () => void; + onShare: () => void; + onBookmark: () => void; + showViews?: boolean; + showShare?: boolean; + showBookmark?: boolean; +} + +/** + * PostVotePreview 子组件 Props + */ +export interface PostVotePreviewProps { + isVote: boolean; + totalVotes?: number; + optionsCount?: number; + onPress: () => void; +} + +/** + * PostTopComment 子组件 Props + */ +export interface PostTopCommentProps { + comment: CommentDTO | null; + totalComments: number; + onPress: () => void; +} + +/** + * PostGridCover 子组件 Props + */ +export interface PostGridCoverProps { + image: PostImageDTO | null; + content?: string; + isVote?: boolean; + aspectRatio: number; + onImagePress: () => void; +} + +/** + * PostGridInfo 子组件 Props + */ +export interface PostGridInfoProps { + title?: string; + author: UserDTO; + likesCount: number; + onUserPress: () => void; +} + +// ==================== 容器组件 Props ==================== + +/** + * PostCardList 容器组件 Props + */ +export interface PostCardListProps { + post: Post; + onAction: PostCardOnAction; + features: PostCardFeatures; + isPostAuthor?: boolean; + isAuthor?: boolean; + index?: number; + style?: StyleProp; +} + +/** + * PostCardGrid 容器组件 Props + */ +export interface PostCardGridProps { + post: Post; + onAction: PostCardOnAction; + features: PostCardFeatures; + style?: StyleProp; +} + +// ==================== 旧版 Props(兼容层使用) ==================== + +/** + * 旧版 PostCard Props(用于兼容层) + * @deprecated 请迁移到新的 PostCardProps + */ +export interface LegacyPostCardProps { + post: Post; + onPress: () => void; + onUserPress: () => void; + onLike: () => void; + onComment: () => void; + onBookmark: () => void; + onShare: () => void; + onImagePress?: (images: ImageGridItem[], index: number) => void; + onDelete?: () => void; + compact?: boolean; + index?: number; + isAuthor?: boolean; + isPostAuthor?: boolean; + variant?: 'default' | 'grid'; +} + +// ==================== 工具类型 ==================== + +/** + * 徽章类型 + */ +export interface Badge { + type: 'author' | 'admin'; + label: string; +} diff --git a/src/components/business/index.ts b/src/components/business/index.ts index 7b319e5..5547a0d 100644 --- a/src/components/business/index.ts +++ b/src/components/business/index.ts @@ -2,7 +2,21 @@ * 业务组件导出 */ +// PostCard 新架构(从 PostCard 目录导出) export { default as PostCard } from './PostCard'; +// 导出 PostCard 相关类型和工具 +export type { + PostCardActionType, + PostCardAction, + PostCardOnAction, + PostCardFeatures, + PostCardFeaturesPreset, + PostCardFeaturesConfig, + PostCardProps, + LegacyPostCardProps, +} from './PostCard'; +export { PostCardPresets, resolveFeatures } from './PostCard'; +export { usePostCardActions, usePostCardFeatures } from './PostCard'; export { default as CommentItem } from './CommentItem'; export { default as UserProfileHeader } from './UserProfileHeader'; export { default as SystemMessageItem } from './SystemMessageItem'; diff --git a/src/screens/home/HomeScreen.tsx b/src/screens/home/HomeScreen.tsx index bbf9134..288e50e 100644 --- a/src/screens/home/HomeScreen.tsx +++ b/src/screens/home/HomeScreen.tsx @@ -29,6 +29,7 @@ import { useUserStore } from '../../stores'; import { useCurrentUser } from '../../stores/authStore'; import { 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'; import { HomeStackParamList, RootStackParamList } from '../../navigation/types'; import { useResponsive, useResponsiveSpacing } from '../../hooks/useResponsive'; @@ -368,6 +369,41 @@ export const HomeScreen: React.FC = () => { setShowImageViewer(true); }; + // 统一处理 PostCard 的所有操作 + const handlePostAction = (post: Post, action: PostCardAction) => { + switch (action.type) { + case 'press': + handlePostPress(post.id); + break; + case 'userPress': + const authorId = post.author?.id; + if (authorId) handleUserPress(authorId); + break; + case 'like': + case 'unlike': + handleLike(post); + break; + case 'comment': + handlePostPress(post.id, true); + break; + case 'bookmark': + case 'unbookmark': + handleBookmark(post); + break; + case 'share': + handleShare(post); + break; + case 'imagePress': + if (action.payload?.images && action.payload?.imageIndex !== undefined) { + handleImagePress(action.payload.images, action.payload.imageIndex); + } + break; + case 'delete': + handleDeletePost(post.id); + break; + } + }; + // 跳转到发帖页面(使用 Modal 方式) const handleCreatePost = () => { setShowCreatePost(true); @@ -392,19 +428,12 @@ export const HomeScreen: React.FC = () => { ]}> handlePostPress(item.id)} - onUserPress={() => authorId && handleUserPress(authorId)} - onLike={() => handleLike(item)} - onComment={() => handlePostPress(item.id, true)} - onBookmark={() => handleBookmark(item)} - onShare={() => handleShare(item)} - onImagePress={(images, index) => handleImagePress(images, index)} - onDelete={() => handleDeletePost(item.id)} + onAction={(action) => handlePostAction(item, action)} isPostAuthor={isPostAuthor} /> ); - }, [currentUser?.id, handleBookmark, handleDeletePost, handleImagePress, handleLike, handlePostPress, handleShare, handleUserPress, isMobile, listItemWidth, responsiveGap]); + }, [currentUser?.id, handlePostAction, isMobile, listItemWidth, responsiveGap]); // 估算帖子在瀑布流中的高度(用于均匀分配) const estimatePostHeight = (post: Post, columnWidth: number): number => { diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx index 28bdc3a..b100df0 100644 --- a/src/screens/profile/ProfileScreen.tsx +++ b/src/screens/profile/ProfileScreen.tsx @@ -22,6 +22,7 @@ import { Post } from '../../types'; import { useAuthStore, useUserStore } from '../../stores'; import { postService } from '../../services'; import { UserProfileHeader, PostCard, TabBar } from '../../components/business'; +import { PostCardAction } from '../../components/business/PostCard'; import { Loading, EmptyState, Text } from '../../components/common'; import { ResponsiveContainer } from '../../components/common'; import { useResponsive } from '../../hooks'; @@ -245,6 +246,37 @@ export const ProfileScreen: React.FC = () => { } }, []); + // 统一的 PostCard Action 处理 + const handlePostAction = useCallback((post: Post, action: PostCardAction) => { + switch (action.type) { + case 'press': + handlePostPress(post.id); + break; + case 'userPress': + if (post.author) { + handleUserPress(post.author.id); + } + break; + case 'like': + case 'unlike': + post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id); + break; + case 'comment': + handlePostPress(post.id, true); + break; + case 'bookmark': + case 'unbookmark': + post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id); + break; + case 'share': + // 暂不处理分享 + break; + case 'delete': + handleDeletePost(post.id); + break; + } + }, [handlePostPress, handleUserPress, handleDeletePost]); + // 渲染内容 const renderContent = useCallback(() => { if (loading) return ; @@ -273,13 +305,7 @@ export const ProfileScreen: React.FC = () => { ]}> handlePostPress(post.id)} - onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}} - onLike={() => post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id)} - onComment={() => handlePostPress(post.id, true)} - onBookmark={() => post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id)} - onShare={() => {}} - onDelete={() => handleDeletePost(post.id)} + onAction={(action) => handlePostAction(post, action)} isPostAuthor={isPostAuthor} /> diff --git a/src/screens/profile/UserScreen.tsx b/src/screens/profile/UserScreen.tsx index 5383caf..ffd5d17 100644 --- a/src/screens/profile/UserScreen.tsx +++ b/src/screens/profile/UserScreen.tsx @@ -23,6 +23,7 @@ import { useCurrentUser } from '../../stores/authStore'; import { authService, postService, messageService } from '../../services'; import { userManager } from '../../stores/userManager'; import { UserProfileHeader, PostCard, TabBar } from '../../components/business'; +import { PostCardAction } from '../../components/business/PostCard'; import { Loading, EmptyState, ResponsiveContainer } from '../../components/common'; import { useResponsive } from '../../hooks'; import { HomeStackParamList, RootStackParamList } from '../../navigation/types'; @@ -218,6 +219,39 @@ export const UserScreen: React.FC = () => { } }; + // 统一处理 PostCard 的所有操作 + const handlePostAction = (post: Post, action: PostCardAction) => { + switch (action.type) { + case 'press': + handlePostPress(post.id); + break; + case 'userPress': + if (post.author) handleUserPress(post.author.id); + break; + case 'like': + postService.likePost(post.id); + break; + case 'unlike': + postService.unlikePost(post.id); + break; + case 'comment': + handlePostPress(post.id, true); + break; + case 'bookmark': + postService.favoritePost(post.id); + break; + case 'unbookmark': + postService.unfavoritePost(post.id); + break; + case 'share': + // 暂不处理 + break; + case 'delete': + handleDeletePost(post.id); + break; + } + }; + // 跳转到聊天界面 const handleMessage = async () => { if (!user) return; @@ -317,13 +351,7 @@ export const UserScreen: React.FC = () => { ]}> handlePostPress(post.id)} - onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}} - onLike={() => post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id)} - onComment={() => handlePostPress(post.id, true)} - onBookmark={() => post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id)} - onShare={() => {}} - onDelete={() => handleDeletePost(post.id)} + onAction={(action) => handlePostAction(post, action)} isPostAuthor={isPostAuthor} /> @@ -357,13 +385,7 @@ export const UserScreen: React.FC = () => { ]}> handlePostPress(post.id)} - onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}} - onLike={() => post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id)} - onComment={() => handlePostPress(post.id, true)} - onBookmark={() => post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id)} - onShare={() => {}} - onDelete={() => handleDeletePost(post.id)} + onAction={(action) => handlePostAction(post, action)} isPostAuthor={isPostAuthor} />