diff --git a/docs/20260625_메인_홈_팔로잉_탭/prd.md b/docs/20260625_메인_홈_팔로잉_탭/prd.md index 34ea9901..37ce90f2 100644 --- a/docs/20260625_메인_홈_팔로잉_탭/prd.md +++ b/docs/20260625_메인_홈_팔로잉_탭/prd.md @@ -117,15 +117,49 @@ data class FollowingScheduleResponse( data class FollowingNewsResponse( @SerializedName("newsId") val newsId: String, @SerializedName("type") val type: FollowingNewsType, - @SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String, - @SerializedName("creatorNickname") val creatorNickname: String, - @SerializedName("title") val title: String, - @SerializedName("body") val body: String, - @SerializedName("thumbnailImageUrl") val thumbnailImageUrl: String?, - @SerializedName("targetId") val targetId: Long, - @SerializedName("occurredAtUtc") val occurredAtUtc: String, @SerializedName("visibleFromAtUtc") val visibleFromAtUtc: String, - @SerializedName("rank") val rank: Int? + @SerializedName("creatorRanking") val creatorRanking: FollowingCreatorRankingNewsResponse?, + @SerializedName("audioContent") val audioContent: FollowingContentNewsResponse?, + @SerializedName("photoContent") val photoContent: FollowingContentNewsResponse?, + @SerializedName("contentRanking") val contentRanking: FollowingContentRankingNewsResponse?, + @SerializedName("communityPost") val communityPost: FollowingCommunityPostNewsResponse? +) + +@Keep +data class FollowingCreatorRankingNewsResponse( + @SerializedName("rank") val rank: Int, + @SerializedName("creatorId") val creatorId: Long, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String +) + +@Keep +data class FollowingContentNewsResponse( + @SerializedName("contentId") val contentId: Long, + @SerializedName("contentImageUrl") val contentImageUrl: String?, + @SerializedName("title") val title: String, + @SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String, + @SerializedName("creatorNickname") val creatorNickname: String +) + +@Keep +data class FollowingContentRankingNewsResponse( + @SerializedName("rank") val rank: Int, + @SerializedName("contentId") val contentId: Long, + @SerializedName("contentImageUrl") val contentImageUrl: String?, + @SerializedName("title") val title: String +) + +@Keep +data class FollowingCommunityPostNewsResponse( + @SerializedName("postId") val postId: Long, + @SerializedName("creatorProfileImage") val creatorProfileImage: String, + @SerializedName("creatorNickname") val creatorNickname: String, + @SerializedName("imageUrl") val imageUrl: String?, + @SerializedName("content") val content: String, + @SerializedName("createdAt") val createdAt: String, + @SerializedName("likeCount") val likeCount: Int, + @SerializedName("commentCount") val commentCount: Int ) enum class FollowingNewsType { @@ -226,21 +260,27 @@ Figma `24:5682` 기준 상단 title bar와 tab bar는 기존 홈 화면 구조 `recentNews`를 타입별 feed 카드로 표시한다. #### Requirements -- `CREATOR_RANKING`은 creator ranking 소식으로 표시하고 `rank` 값을 강조한다. -- `CONTENT_RANKING`은 content ranking 소식으로 표시하고 `rank` 값을 강조한다. -- `CREATOR_RANKING`, `CONTENT_RANKING`에서 `rank`가 null이면 해당 ranking news item은 표시하지 않는다. +- `CREATOR_RANKING`은 `creatorRanking` payload로 creator ranking 소식을 표시하고 `rank`, `nickname`, `profileImageUrl`, `creatorId`를 사용한다. +- `CONTENT_RANKING`은 `contentRanking` payload로 content ranking 소식을 표시하고 `rank`, `contentId`, `contentImageUrl`, `title`을 사용한다. +- `AUDIO_CONTENT`는 `audioContent` payload로 오디오 콘텐츠 feed를 표시하고 `contentId`, `contentImageUrl`, `title`, `creatorProfileImageUrl`, `creatorNickname`을 사용한다. +- `PHOTO_CONTENT`는 `photoContent` payload로 화보 콘텐츠 feed를 표시하고 `contentId`, `contentImageUrl`, `title`, `creatorProfileImageUrl`, `creatorNickname`을 사용한다. +- `COMMUNITY_POST`는 `communityPost` payload로 커뮤니티 feed를 표시하고 `postId`, `creatorProfileImage`, `creatorNickname`, `imageUrl`, `content`, `createdAt`, `likeCount`, `commentCount`를 사용한다. +- `type`에 대응하는 payload가 null이면 해당 news item은 표시하지 않는다. - 초기 운영 기간에는 `CREATOR_RANKING` 위주로 내려올 예정이다. -- `COMMUNITY_POST`는 커뮤니티 feed 형태로 표시한다. -- `AUDIO_CONTENT`는 오디오 콘텐츠 feed 형태로 표시한다. - `PHOTO_CONTENT`는 우선 `화보` label로 표시한다. 이 label은 이후 기획 변경 가능성이 있다. - 시간 표시는 `visibleFromAtUtc`를 기준으로 디바이스 타임존으로 변환한 뒤 상대 시간으로 표시한다. -- `thumbnailImageUrl`이 null이면 타입별 기본 이미지 표시/숨김 정책을 구현 계획에서 확정한다. -- item 터치 시 `type`과 `targetId`에 맞는 목적지로 이동한다. +- `contentImageUrl` 또는 `imageUrl`이 null이면 타입별 기본 이미지 표시/숨김 정책을 구현 계획에서 확정한다. +- item 터치 시 `type`과 payload의 target id(`creatorId`, `contentId`, `postId`)에 맞는 목적지로 이동한다. - 리스트가 비어 있으면 섹션을 숨긴다. - 기존 `FeedAdapter`, `FeedItem.Rank`, `FeedItem.Community`, `FeedItem.Content`, `FeedRankView`, `FeedCommunityView`, `FeedContentView` 재사용 가능성을 우선 검토한다. - `PHOTO_CONTENT`는 기존 `FeedItem.Content`의 category 확장 또는 팔로잉 전용 item 추가 중 더 작은 변경을 구현 계획에서 선택한다. - 각 섹션의 “더보기” chevron은 터치 액션만 연결하고, 실제 이동 목적지는 아직 만들지 않는다. +#### 2026-06-30 Response 변경 Requirements +- 기존 최근 소식 공통 평면 필드(`creatorProfileImageUrl`, `creatorNickname`, `title`, `body`, `thumbnailImageUrl`, `targetId`, `occurredAtUtc`, `rank`)는 사용하지 않고, `type`별 nested payload를 사용한다. +- Android DTO, mapper, UI model, adapter binding, 테스트 fixture는 새 nested payload 구조를 기준으로 갱신한다. +- 새 응답 변경은 최근 소식 섹션에만 적용하며, 팔로잉 크리에이터, On Air, 최근 대화, 이달의 스케줄, 로그인 유도, API endpoint, optional authorization 정책은 변경하지 않는다. + #### 2026-06-30 후속 수정 Requirements - `CREATOR_RANKING` 최근 소식 본문은 순위 숫자만 단독 표시하지 않고, Figma `24:5717` 기준으로 크리에이터 이름과 순위를 포함한 문장형 메시지를 표시한다. - ranking 문장 안의 순위 텍스트는 기존 `FeedRankTextStyler` highlight range로 강조한다.