29 KiB
29 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("visibleFromAtUtc") val visibleFromAtUtc: String,
@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 {
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 터치 시 해당 크리에이터 채널로 이동한다.
followingCreators가 1명 이상이면 추천 탭 라이브 섹션의전체와 동일하게 크리에이터 가로 RecyclerView 우측 끝에 고정TextView로전체버튼을 항상 표시한다.전체버튼은 RecyclerView의 마지막 item으로 포함하지 않는다. 팔로잉 크리에이터 목록만 좌측 가로 스크롤 영역에 남고,전체버튼은 스크롤되지 않아야 한다.전체버튼은 추천 탭 라이브 섹션의tv_home_live_all구조처럼 section root를 horizontal container로 두고,RecyclerView는layout_width="0dp",layout_weight="1",전체TextView는 우측 고정 폭과 end margin을 가진다.전체버튼 터치 시 기존 전체 팔로잉 크리에이터 목록 화면으로 이동한다.- 리스트가 비어 있으면 섹션을 숨긴다.
- 이미지 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은creatorRankingpayload로 creator ranking 소식을 표시하고rank,nickname,profileImageUrl,creatorId를 사용한다.CONTENT_RANKING은contentRankingpayload로 content ranking 소식을 표시하고rank,contentId,contentImageUrl,title을 사용한다.AUDIO_CONTENT는audioContentpayload로 오디오 콘텐츠 feed를 표시하고contentId,contentImageUrl,title,creatorProfileImageUrl,creatorNickname을 사용한다.PHOTO_CONTENT는photoContentpayload로 화보 콘텐츠 feed를 표시하고contentId,contentImageUrl,title,creatorProfileImageUrl,creatorNickname을 사용한다.COMMUNITY_POST는communityPostpayload로 커뮤니티 feed를 표시하고postId,creatorProfileImage,creatorNickname,imageUrl,content,createdAt,likeCount,commentCount를 사용한다.type에 대응하는 payload가 null이면 해당 news item은 표시하지 않는다.- 초기 운영 기간에는
CREATOR_RANKING위주로 내려올 예정이다. PHOTO_CONTENT는 우선화보label로 표시한다. 이 label은 이후 기획 변경 가능성이 있다.- 시간 표시는
visibleFromAtUtc를 기준으로 디바이스 타임존으로 변환한 뒤 상대 시간으로 표시한다. contentImageUrl또는imageUrl이 null이면 타입별 기본 이미지 표시/숨김 정책을 구현 계획에서 확정한다.- item 터치 시
type과 payload의 target id(creatorId,contentId,postId)에 맞는 목적지로 이동한다. - 리스트가 비어 있으면 섹션을 숨긴다.
- 기존
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-07-12 COMMUNITY_POST 상세 이동 Requirements
- 최근 소식 섹션의
COMMUNITY_POSTitem 터치 시communityPost.postId를 사용해CreatorChannelCommunityDetailActivity로 이동한다. - 이동은 기존 MainV2 로그인 가드(
ensureMainV2NavigationAllowed)를 통과한 뒤 실행한다. COMMUNITY_POST가 아닌 최근 소식 item은 이번 작업 범위에서 기존처럼 이동하지 않는다.- legacy
CreatorCommunityAllActivity나Constants.EXTRA_COMMUNITY_POST_ID직접 extra 조립 경로는 사용하지 않는다.
2026-07-13 전체 empty 문구 Requirements
isLoginRequired = false이고followingCreators,onAirLives,recentChats,monthlySchedules,recentNews가 모두 비어 있는HomeFollowingUiState.Empty상태에서는 팔로잉 탭 본문 중앙에 empty 문구를 표시한다.- empty 문구는 기존
screen_home_following_emptystring resource를 재사용해 수정한다. - 한국어 기본 문구는
아직 팔로잉 소식이 없어요.\n관심 있는 크리에이터를 팔로우해 보세요.로 표시한다. - 영어 문구는
No following updates yet.\nFollow creators you are interested in.로 표시한다. - 일본어 문구는
フォロー中のお知らせはまだありません。\n気になるクリエイターをフォローしてみましょう。로 표시한다. - empty 문구
TextView는 팔로잉 탭 content 영역 전체의 가운데에 배치하고, 텍스트도 가운데 정렬한다. HomeFollowingUiState.Content에서는 empty 문구를 숨기고 기존 섹션별 visibility 정책을 유지한다.HomeFollowingUiState.LoginRequired와Error는 이번 범위에서 empty 문구를 표시하지 않고 기존처럼 섹션을 숨긴다.- API/DTO/mapper 계약과 팔로잉 크리에이터
전체버튼 노출 정책은 변경하지 않는다.
2026-07-30 CREATOR_RANKING·AUDIO_CONTENT 상세 이동 Requirements
- 최근 소식 섹션의
CREATOR_RANKINGitem 터치 시creatorRanking.creatorId에 해당하는 크리에이터 채널로 이동한다. - 최근 소식 섹션의
AUDIO_CONTENTitem 터치 시audioContent.contentId에 해당하는 오디오 콘텐츠 상세 화면으로 이동한다. - 크리에이터 채널 이동은 기존
CreatorActionCommand.Profile, 오디오 콘텐츠 상세 이동은 기존ContentActionCommand.AudioDetail공통 액션 경로를 재사용한다. - 공통 액션 경로의 로그인·콘텐츠 접근 정책과 유효 ID 검증을 유지하며 직접
Intent또는 legacy extra를 새로 조립하지 않는다. - 기존
COMMUNITY_POST상세 이동은 유지하고,CONTENT_RANKING과PHOTO_CONTENT의 터치 동작은 이번 범위에서 추가하지 않는다. - API/DTO/mapper, 최근 소식 표시 UI와 다른 팔로잉 섹션의 동작은 변경하지 않는다.
2026-08-18 팔로잉 크리에이터 전체 버튼 고정 Requirements
- 팔로잉 탭 최상단 팔로잉 크리에이터 섹션의
전체버튼은 추천 탭 라이브 섹션과 동일하게 RecyclerView 밖 우측 끝에 고정한다. - 기존 RecyclerView 마지막 item 방식의
전체노출은 더 이상 사용하지 않는다. - 팔로잉 크리에이터 프로필 item만 수평 스크롤되며,
전체버튼은 사용자가 리스트를 좌우로 스크롤해도 화면 우측에 남아 있어야 한다. followingCreators가 비어 있으면 기존처럼 팔로잉 크리에이터 섹션 전체를 숨기며, 고정전체버튼도 보이지 않아야 한다.전체버튼 클릭 동작은 기존FollowingCreatorActivity진입 동작을 유지한다.- 추천 탭과 동일한 구조 기준은
fragment_v2_main_home.xml의ll_home_live_section,rv_home_lives,tv_home_live_all배치 방식이다. - 이번 요구사항은 팔로잉 크리에이터 섹션의 배치 방식만 변경하며 API/DTO/mapper, 다른 팔로잉 섹션, 최근 소식 라우팅, empty/login/error 정책은 변경하지 않는다.
2026-08-19 팔로잉 empty 안내 문구 변경 Requirements
isLoginRequired = false이고followingCreators,onAirLives,recentChats,monthlySchedules,recentNews가 모두 비어 있는 기존HomeFollowingUiState.Empty판정 조건을 유지한다.followingCreators만 비어 있는 상태를 별도 empty 조건으로 추가하지 않는다. 팔로우한 크리에이터가 없으면 나머지 네 섹션도 비어 있는 서버 동작을 전제로 한다.- 기존
screen_home_following_emptystring resource와tv_home_following_empty를 재사용한다. - 한국어 문구는
아직 팔로우한 크리에이터가 없어요.\n관심 있는 크리에이터를 팔로우해 보세요.로 표시한다. - 일본어 문구는
まだフォローしているクリエイターがいません。\n気になるクリエイターをフォローしてみましょう。로 표시한다. - 영어 문구
No following updates yet.\nFollow creators you are interested in.은 변경하지 않는다. - 안내 문구는 기존처럼 팔로잉 탭 본문 가운데에 표시하고 텍스트를 가운데 정렬한다.
- 안내 문구가 표시된 상태에서도 기존
SwipeRefreshLayout경로로 당겨서 새로고침할 수 있어야 하며, 새Empty응답을 받으면 refresh 상태를 종료하고 안내 문구를 계속 표시한다. - API/DTO/mapper,
HomeFollowingUiState, 팔로잉 section visibility, layout 구조,Content·LoginRequired·Error처리는 변경하지 않는다.
2026-06-30 후속 수정 Requirements
CREATOR_RANKING최근 소식 본문은 순위 숫자만 단독 표시하지 않고, Figma24:5717기준으로 크리에이터 이름과 순위를 포함한 문장형 메시지를 표시한다.- ranking 문장 안의 순위 텍스트는 기존
FeedRankTextStylerhighlight range로 강조한다. AUDIO_CONTENT최근 소식은 Figma1229:27212기준으로88dp콘텐츠 이미지,14dp카드 padding/gap,20dp프로필 이미지, 프로필과 제목 사이6dp간격을 유지한다.- 이번 수정은 팔로잉 탭 최근 소식 feed 표시 정합성 보완으로 한정하고 API 계약, 라우팅, 로그인 유도 정책은 변경하지 않는다.
재사용 가능한 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
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 실제 이동 목적지는 아직 만들지 않는다. 이번 범위에서는 터치 액션 연결까지만 지정한다.
12. Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·계획 |
|---|---|---|---|---|---|
| 2026-08-19 | DEC-20260819-001 |
확정 | 기존 5개 섹션 전체 empty 판정을 유지하고 한국어·일본어 안내 문구의 첫 문장만 변경하며 영어 문구는 유지한다. | 사용자 인터뷰에서 서버가 팔로우한 크리에이터가 없을 때 5개 섹션을 모두 빈 배열로 반환하는 동작을 전제로 확정했다. | 2026-08-19 팔로잉 empty 안내 문구 변경 Requirements, P15-T1, P15-GATE |