19 KiB
19 KiB
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/followingpermitAll설정이 필요하다. - 컨트롤러에서
member == null이면isLoginRequired = true와 빈 섹션 배열을 담은 응답을 반환한다.
Android Response Contract
@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재사용을 우선 검토한다.Authorizationheader는 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_titleinclude와ViewSectionTitleBindinghelper 패턴 재사용을 우선 검토한다. - 각 섹션 리스트가 비어 있으면 해당 섹션은 숨기는 것을 기본 정책으로 한다.
로그인 유도 화면
비로그인 사용자는 팔로잉 섹션 대신 로그인 유도 화면을 본다.
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>, RxJava3Single,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 실제 이동 목적지는 아직 만들지 않는다. 이번 범위에서는 터치 액션 연결까지만 지정한다.