# PRD: 메인 홈 팔로잉 탭 ## 1. Overview Figma `home_003` 팔로잉 탭 화면(`24:5682`)을 기준으로 메인 홈의 `팔로잉` 탭 본문을 구성하고, `GET /api/v2/home/following` 응답을 기존 v2 widget 중심으로 바인딩한다. --- ## 2. Problem - `HomeMainFragment`는 이미 `추천`, `랭킹`, `팔로잉` 탭을 표시하지만, 팔로잉 탭 선택 시 본문 전환 로직이 아직 구현되어 있지 않다. - 팔로잉 탭은 로그인 사용자에게 팔로잉 크리에이터, On Air, 최근 대화, 이달의 스케줄, 최근 소식을 한 화면에서 보여줘야 한다. - 같은 API endpoint는 비로그인 조회도 허용하며, 비로그인 사용자는 실제 섹션 데이터 대신 로그인 유도 화면을 봐야 한다. - Figma의 여러 요소는 기존 v2 widget과 유사하므로, 신규 UI를 만들기 전에 `v2` 패키지 하위의 재사용 가능 후보를 확인해야 한다. - 서버 응답에는 `ChatRoomListItemResponse`, `CreatorActivityType`처럼 기존 앱 타입과 연결될 수 있는 항목이 포함되므로, 기존 모델/mapper와 중복되지 않게 설계해야 한다. --- ## 3. Goals - 팔로잉 탭 선택 시 `GET /api/v2/home/following`을 호출하고 응답 상태에 따라 팔로잉 본문 또는 로그인 필요 상태로 분기한다. - 비로그인 응답의 `isLoginRequired = true`를 앱 상태로 매핑하고, 팔로잉 탭 본문 섹션을 숨긴다. - 로그인 응답의 `followingCreators`, `onAirLives`, `recentChats`, `monthlySchedules`, `recentNews`를 Figma 섹션 순서에 맞게 표시한다. - `GET /api/v2/home/following`은 query parameter 없이 호출한다. - 앱 DTO는 현재 Android 클라이언트 관례에 맞춰 Gson `@SerializedName` 기반으로 작성한다. - 기존 `HomeMainFragment`, `TextTabBarView`, `view_section_title`, v2 widget/adapter 재사용을 우선 검토한다. - 재사용이 어려운 영역만 팔로잉 탭 전용 adapter item 또는 view로 최소 구현한다. - API DTO, UI state, mapper, empty/error/loading 정책, click routing은 후속 `plan-task.md`에서 검증 가능하도록 정리한다. --- ## 4. Non-Goals - 이번 PRD 작성 단계에서는 코드, 리소스, 레이아웃 파일을 구현하지 않는다. - Android 저장소에서 서버 `SecurityConfig`를 직접 수정하지 않는다. 단, 백엔드 전제 조건으로 `GET /api/v2/home/following`의 `permitAll` 요구사항은 기록한다. - 팔로잉/언팔로잉 액션, 전체 팔로잉 관리, 스케줄 생성/수정, 채팅방 생성 API는 이번 범위에 포함하지 않는다. - API pagination, cursor, filter, sort query parameter는 추가하지 않는다. - Figma에 없는 skeleton loading, shimmer, 임의 애니메이션, 추가 badge는 만들지 않는다. - 기존 레거시 홈 화면 전체 리팩터링은 포함하지 않는다. - Compose 화면으로 전환하지 않는다. - 백엔드 응답 필드명, enum 이름, 정렬 기준은 앱에서 임의 변경하지 않는다. --- ## 5. Target Users - 팔로우한 크리에이터의 라이브, 대화, 스케줄, 소식을 메인 홈에서 빠르게 확인하려는 로그인 사용자. - 팔로잉 탭에 접근했지만 아직 로그인하지 않아 로그인 필요 안내를 받아야 하는 비로그인 사용자. - 기존 XML View와 v2 widget을 재사용해 홈 팔로잉 화면을 구현/유지보수하는 Android 개발자. --- ## 6. User Stories - 사용자는 홈의 `팔로잉` 탭에서 내가 팔로우한 크리에이터 목록을 가로로 탐색하고 싶다. - 사용자는 팔로우 중인 크리에이터가 라이브 중이면 `On Air` 섹션에서 바로 확인하고 라이브로 이동하고 싶다. - 사용자는 최근 대화한 채팅방을 팔로잉 탭에서 확인하고 대화방으로 진입하고 싶다. - 사용자는 팔로우한 크리에이터의 이번 달 예정 콘텐츠와 현재 On Air 상태를 확인하고 싶다. - 사용자는 최근 소식에서 랭킹, 커뮤니티, 오디오, 화보 콘텐츠 업데이트를 한 흐름으로 확인하고 싶다. - 비로그인 사용자는 팔로잉 탭 접근 시 빈 화면이 아니라 로그인하면 볼 수 있다는 안내와 로그인 진입점을 기대한다. - 개발자는 기존 v2 widget을 최대한 재사용해 화면별 UI 중복과 스타일 차이를 줄이고 싶다. --- ## 7. Core Features ### 팔로잉 홈 API 연동 팔로잉 탭은 단일 API 응답을 섹션별 UI model로 변환해 표시한다. #### Endpoint Contract - Method: `GET` - Path: `/api/v2/home/following` - Header: `Authorization: Bearer {accessToken}` optional - Query parameter: 없음 #### Backend Preconditions - 비로그인 조회를 허용한다. - 서버 `SecurityConfig`에 `GET /api/v2/home/following` `permitAll` 설정이 필요하다. - 컨트롤러에서 `member == null`이면 `isLoginRequired = true`와 빈 섹션 배열을 담은 응답을 반환한다. #### Android Response Contract ```kotlin @Keep data class HomeFollowingTabResponse( @SerializedName("isLoginRequired") val isLoginRequired: Boolean, @SerializedName("followingCreators") val followingCreators: List, @SerializedName("onAirLives") val onAirLives: List, @SerializedName("recentChats") val recentChats: List, @SerializedName("monthlySchedules") val monthlySchedules: List, @SerializedName("recentNews") val recentNews: List ) @Keep data class FollowingCreatorResponse( @SerializedName("creatorId") val creatorId: Long, @SerializedName("creatorNickname") val creatorNickname: String, @SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String ) @Keep data class FollowingLiveResponse( @SerializedName("liveId") val liveId: Long, @SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String, @SerializedName("creatorNickname") val creatorNickname: String, @SerializedName("title") val title: String, @SerializedName("startedAtUtc") val startedAtUtc: String ) @Keep data class FollowingScheduleResponse( @SerializedName("scheduleId") val scheduleId: String, @SerializedName("creatorId") val creatorId: Long, @SerializedName("creatorProfileImageUrl") val creatorProfileImageUrl: String, @SerializedName("creatorNickname") val creatorNickname: String, @SerializedName("title") val title: String, @SerializedName("type") val type: CreatorActivityType, @SerializedName("targetId") val targetId: Long, @SerializedName("scheduledAtUtc") val scheduledAtUtc: String, @SerializedName("isOnAir") val isOnAir: Boolean ) @Keep 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? ) enum class FollowingNewsType { CREATOR_RANKING, CONTENT_RANKING, COMMUNITY_POST, AUDIO_CONTENT, PHOTO_CONTENT } ``` #### Android DTO Requirements - 위 응답 예시는 서버 응답 형태를 현재 Android 프로젝트의 DTO 관례로 옮긴 기준이다. - DTO 작성 시 `androidx.annotation.Keep`, `com.google.gson.annotations.SerializedName`을 사용한다. - DTO 파일은 `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/` 하위에 둔다. - API 인터페이스는 기존 `HomeRecommendationApi`, `HomeCreatorRankingApi`처럼 v2 홈 전용 Retrofit API로 추가한다. - Repository/ViewModel은 기존 `Api -> Repository -> ViewModel -> Fragment` 흐름을 따른다. - `recentChats`는 기존 `ChatRoomListItemResponse`와 필드 계약이 같으면 해당 타입 재사용을 우선 검토한다. - `CreatorActivityType`은 기존 `kr.co.vividnext.sodalive.v2.common.CreatorActivityType` 재사용을 우선 검토한다. - `Authorization` header는 optional이어야 하므로, 토큰이 비어 있을 때 `"Bearer "` 문자열을 보내지 않는 방식을 구현 계획에서 확정한다. - 응답 DTO를 화면에 직접 노출하지 않고 팔로잉 탭 전용 UI state/model로 변환한다. #### State Requirements - `isLoginRequired = true`이면 섹션 리스트 값과 무관하게 로그인 유도 상태로 처리한다. - `isLoginRequired = false`이고 모든 섹션 리스트가 비어 있으면 로그인 유도 화면이 아니라 로그인 사용자용 empty 상태로 처리한다. - API 실패 시 기존 v2 홈 화면의 error/toast/loading 패턴을 따른다. - 탭 최초 선택 시 1회 로드하고, 후속 새로고침 정책은 구현 계획에서 기존 홈 탭 패턴을 기준으로 결정한다. ### 팔로잉 탭 UI 구성 Figma `24:5682` 기준 상단 title bar와 tab bar는 기존 홈 화면 구조를 유지하고, `팔로잉` 탭 선택 상태의 본문만 추가한다. #### Requirements - title bar는 기존 `view_title_bar_home`을 유지한다. - tab bar는 기존 `TextTabBarView`를 사용한다. - tab 항목은 `추천`, `랭킹`, `팔로잉` 순서로 유지한다. - `팔로잉` tab 선택 시 추천 content와 랭킹 content는 숨기고 팔로잉 content를 표시한다. - 세로 스크롤 시 title bar와 `TextTabBarView`는 화면에 유지하고, `TextTabBarView` 아래 팔로잉 content 영역만 스크롤되도록 구성한다. - 섹션 타이틀은 기존 `view_section_title` include와 `ViewSectionTitleBinding` helper 패턴 재사용을 우선 검토한다. - 각 섹션 리스트가 비어 있으면 해당 섹션은 숨기는 것을 기본 정책으로 한다. ### 로그인 유도 화면 비로그인 사용자는 팔로잉 섹션 대신 로그인 유도 화면을 본다. #### Requirements - `isLoginRequired = true`일 때 팔로잉 탭 본문 섹션 배열을 표시하지 않는다. - 로그인 유도 화면의 정확한 디자인과 문구는 아직 정해지지 않았다. - 구현 계획에서는 `isLoginRequired = true` 상태 분기와 팔로잉 섹션 숨김 처리까지만 확정하고, 로그인 유도 UI는 별도 확정 후 구현 가능한 영역으로 분리한다. - 로그인 유도 화면이 확정되면 문구는 string resource로 관리하고 `values`, `values-en`, `values-ja` 반영을 검토한다. ### 팔로잉 크리에이터 섹션 `followingCreators`를 Figma 상단의 프로필 원형 리스트로 표시한다. #### Requirements - profile image와 creator nickname을 표시한다. - item 터치 시 해당 크리에이터 채널로 이동한다. - 리스트가 비어 있으면 섹션을 숨긴다. - 이미지 URL이 비어 있거나 로드 실패하면 기존 홈 creator profile placeholder 정책을 따른다. - 기존 `HomeCreatorProfileAdapter`, `HomeRecentActivityCreatorAdapter`, `HomeCreatorProfileImageLoader` 재사용 가능성을 우선 검토한다. ### On Air 섹션 `onAirLives`를 현재 라이브 중인 팔로잉 크리에이터 리스트로 표시한다. #### Requirements - live title, creator nickname, creator profile image, live start time을 표시한다. - `startedAtUtc`는 기존 상대/경과 시간 formatter 재사용을 우선 검토한다. - item 터치 시 해당 live room/detail로 이동한다. - 리스트가 비어 있으면 섹션을 숨긴다. - 기존 `LiveThumbnailSimpleView`, `LiveThumbnailDetailView`, `LiveThumbnailItem`, `HomeLiveAdapter` 재사용 가능성을 우선 검토한다. - Figma의 On Air 카드가 기존 추천 홈의 간단 썸네일보다 상세형에 가까우므로, 기존 detail widget으로 부족한 항목만 팔로잉 전용 adapter에서 보완한다. ### 최근 대화 섹션 `recentChats`를 팔로잉 탭 본문 안의 최근 대화 카드로 표시한다. #### Requirements - `ChatRoomListItemResponse`의 `roomId`, `targetName`, `targetImageUrl`, `lastMessage`, `lastMessageAt`, `chatType`을 사용한다. - item 터치 시 기존 채팅방 진입 flow를 재사용한다. - 리스트가 비어 있으면 섹션을 숨긴다. - 기존 `ChatRoomListAdapter`, `ChatRoomListUiItem`, `ChatRoomMappers`, `item_v2_chat_room.xml` 재사용 가능성을 우선 검토한다. - Figma 팔로잉 탭의 최근 대화 카드는 전체 채팅 탭 리스트보다 작은 가로 카드 형태이므로, 기존 adapter를 그대로 쓰기 어렵다면 mapper와 image/time formatter만 재사용하고 전용 item layout을 최소 추가한다. ### 이달의 스케줄 섹션 `monthlySchedules`를 월간 스케줄 리스트로 표시한다. #### Requirements - schedule date, creator nickname/profile, title, type label, scheduled time 또는 On Air 상태를 표시한다. - `type`은 `CreatorActivityType` 표시 문자열로 변환하고 string resource 기반 다국어 처리를 적용한다. - `isOnAir = true`이면 Figma처럼 `On Air` 상태를 강조 표시한다. - `isOnAir = false`이면 `scheduledAtUtc`를 사용자 지역 시간 기준 시간 텍스트로 표시한다. - 서버는 `monthlySchedules`를 이번 달 범위로 고정 정렬해 내려주며, 앱은 별도 월 필터나 정렬을 수행하지 않는다. - item 터치 시 `type`과 `targetId`에 맞는 목적지로 이동한다. - 리스트가 비어 있으면 섹션을 숨긴다. - Figma의 캘린더/타임라인형 UI와 정확히 맞는 기존 v2 widget은 확인되지 않았으므로 팔로잉 전용 schedule item view를 추가하되, section title, profile image loader, typography/color token은 기존 리소스를 재사용한다. ### 최근 소식 섹션 `recentNews`를 타입별 feed 카드로 표시한다. #### Requirements - `CREATOR_RANKING`은 creator ranking 소식으로 표시하고 `rank` 값을 강조한다. - `CONTENT_RANKING`은 content ranking 소식으로 표시하고 `rank` 값을 강조한다. - `CREATOR_RANKING`, `CONTENT_RANKING`에서 `rank`가 null이면 해당 ranking news item은 표시하지 않는다. - 초기 운영 기간에는 `CREATOR_RANKING` 위주로 내려올 예정이다. - `COMMUNITY_POST`는 커뮤니티 feed 형태로 표시한다. - `AUDIO_CONTENT`는 오디오 콘텐츠 feed 형태로 표시한다. - `PHOTO_CONTENT`는 우선 `화보` label로 표시한다. 이 label은 이후 기획 변경 가능성이 있다. - 시간 표시는 `visibleFromAtUtc`를 기준으로 디바이스 타임존으로 변환한 뒤 상대 시간으로 표시한다. - `thumbnailImageUrl`이 null이면 타입별 기본 이미지 표시/숨김 정책을 구현 계획에서 확정한다. - item 터치 시 `type`과 `targetId`에 맞는 목적지로 이동한다. - 리스트가 비어 있으면 섹션을 숨긴다. - 기존 `FeedAdapter`, `FeedItem.Rank`, `FeedItem.Community`, `FeedItem.Content`, `FeedRankView`, `FeedCommunityView`, `FeedContentView` 재사용 가능성을 우선 검토한다. - `PHOTO_CONTENT`는 기존 `FeedItem.Content`의 category 확장 또는 팔로잉 전용 item 추가 중 더 작은 변경을 구현 계획에서 선택한다. - 각 섹션의 “더보기” chevron은 터치 액션만 연결하고, 실제 이동 목적지는 아직 만들지 않는다. ### 재사용 가능한 v2 위젯 후보 PRD 작성 전 확인한 `v2` 패키지 하위 재사용 후보는 다음과 같다. #### Common/Home Structure - `HomeMainFragment`: 기존 홈 탭 컨테이너, title bar, `TextTabBarView`, 추천/랭킹 전환 구조. - `TextTabBarView`: 홈 내부 `추천`, `랭킹`, `팔로잉` 탭 표시. - `view_section_title` + `ViewSectionTitleBinding`: 섹션 타이틀과 우측 chevron 표시. - `HomeCreatorProfileImageLoader`: 크리에이터 프로필 이미지 로딩/placeholder 정책 후보. #### Creator/Profile - `HomeCreatorProfileAdapter`: 원형 프로필 + 닉네임 리스트 후보. - `HomeRecentActivityCreatorAdapter`: 프로필 원형 리스트와 label 표시 패턴 후보. #### Live - `LiveThumbnailSimpleView`, `LiveThumbnailDetailView`, `LiveThumbnailItem`: On Air 프로필/라이브 카드 후보. - `HomeLiveAdapter`: horizontal live list adapter 패턴 후보. #### Chat - `ChatRoomListAdapter`: 채팅방 row binding, profile image circle crop, click routing 후보. - `ChatRoomListUiItem`, `ChatRoomMappers`, `ChatRoomTimeTextFormatter`: 최근 대화 데이터 변환/시간 표시 후보. #### Feed/News - `FeedAdapter`: rank/live/content/community variant를 가진 feed list 후보. - `FeedRankView`, `FeedContentView`, `FeedCommunityView`: 최근 소식 타입별 카드 후보. - `FeedItem`, `FeedContentCategory`, `FeedRankHighlight`: 최근 소식 UI model 후보. #### Content - `AudioContentCardView`, `SeriesContentCardView`: 최근 소식 콘텐츠 썸네일 표시 보조 후보. - `CreatorActivityType`: 스케줄 type 표시 변환 후보. --- ## 8. UX / UI Expectations - 팔로잉 탭 첫 진입 시 기존 홈 탭과 동일한 검은 배경, spacing, typography, color token을 유지한다. - 섹션 노출 순서는 Figma와 API 응답 순서에 맞춰 `팔로잉 크리에이터` → `On Air` → `최근 대화` → `이달의 스케줄` → `최근 소식` 순서를 기본으로 한다. - 가로 리스트는 Figma처럼 좌우 padding과 item 간격을 유지하고, 세로 스크롤 중 레이아웃이 튀지 않아야 한다. - 이미지 로딩 실패, 빈 URL, 긴 제목/닉네임은 기존 v2 widget의 말줄임/placeholder 정책을 우선 따른다. - `isLoginRequired = true` 상태는 error나 empty가 아니라 명시적인 로그인 필요 상태로 다룬다. - 사용자가 탭을 빠르게 전환해도 loading/dialog/toast가 중복되거나 이전 탭 content가 겹쳐 보이지 않아야 한다. --- ## 9. Technical Constraints - Android XML View 기반으로 구현한다. - 신규 파일과 연결 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다. - 기존 레거시 코드는 직접 수정하지 않고, 필요한 경우 호출 또는 wrapper/adapter로 사용한다. - 기존 `ApiResponse`, RxJava3 `Single`, `BaseViewModel`, Koin DI 패턴을 따른다. - 공개 API 스키마와 서버 필드명은 임의 변경하지 않는다. - API에는 query parameter를 추가하지 않는다. - optional authorization 처리에서 `BuildConfig` 값, token, URL을 로그/Toast/크래시 메시지에 노출하지 않는다. - string resource는 `values`, `values-en`, `values-ja` 반영 필요 여부를 구현 계획에서 확인한다. - 신규 mapper 또는 formatter 로직은 local unit test 검증을 우선한다. --- ## 10. Metrics - 팔로잉 탭 노출 성공률: API success 후 적어도 하나의 섹션 또는 로그인 유도 화면이 정상 표시되는 비율. - 로그인 필요 상태 노출 수: `isLoginRequired = true` 응답을 받은 팔로잉 탭 진입 수. - 팔로잉 섹션 item click: creator, live, chat, schedule, news item별 클릭 이벤트. - API 실패율과 empty 상태 비율. - 팔로잉 탭 최초 렌더링 시간. --- ## 11. Open Questions - 로그인 유도 화면의 정확한 디자인, 문구, CTA, 로그인 완료 후 복귀 정책은 아직 정해지지 않았다. - `PHOTO_CONTENT`의 `화보` label은 임시 정책이며 이후 기획 변경 가능성이 있다. - 각 섹션의 “더보기” chevron 실제 이동 목적지는 아직 만들지 않는다. 이번 범위에서는 터치 액션 연결까지만 지정한다.