Files
sodalive-android/docs/20260625_메인_홈_팔로잉_탭/prd.md

309 lines
19 KiB
Markdown

# 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<FollowingCreatorResponse>,
@SerializedName("onAirLives") val onAirLives: List<FollowingLiveResponse>,
@SerializedName("recentChats") val recentChats: List<ChatRoomListItemResponse>,
@SerializedName("monthlySchedules") val monthlySchedules: List<FollowingScheduleResponse>,
@SerializedName("recentNews") val recentNews: List<FollowingNewsResponse>
)
@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<T>`, 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 실제 이동 목적지는 아직 만들지 않는다. 이번 범위에서는 터치 액션 연결까지만 지정한다.