20 KiB
20 KiB
PRD: 채팅 탭 페이지
1. Overview
ChatMainFragment에 Figma chat_001 기준 채팅 탭 페이지를 구성하고, GET /api/v2/chat/rooms 응답을 채팅방 목록 UI로 표시한다.
2. Problem
ChatMainFragment는 현재FragmentV2MainChatBinding만 연결된 빈 Fragment다.- Figma
177:3466에는 대화 탭의 상단 타이틀, 필터 탭, 채팅방 목록, 플로팅 액션 버튼이 정의되어 있지만 Android 화면에 아직 반영되어 있지 않다. - 기존
v2위젯 중 일부는 재사용할 수 있으나, Figma의 채팅방 list item과 Direct badge는 전용 위젯이 없다. - API 응답 모델은
HomeChatModels.kt에 있으나 채팅 탭 스펙에는ChatRoomList*계열 이름이 더 명확하다.
3. Goals
ChatMainFragment에서 Figmachat_001과 동일한 채팅 탭 페이지 골격을 제공한다.- 기존 위젯과 리소스를 우선 재사용하고, 없는 UI만 최소 신규 위젯으로 만든다.
GET /api/v2/chat/rooms를filter,cursor파라미터와 함께 호출해 채팅방 목록을 표시한다.- 필터 탭
전체,AI 채팅,DM을 제공하고 선택 상태에 맞게 첫 페이지 API를 다시 호출한다. - 채팅방 item 터치 시
chatType=AI는 기존 채팅방 화면으로 이동한다.
4. Non-Goals
- 채팅방 상세 화면, 메시지 송수신, 채팅방 생성 flow 자체는 구현하지 않는다.
chatType=DMitem 클릭 시 이동할 DM 페이지 생성 및 연결은 다음 범위에서 진행한다.- 하단 메인 내비게이션은
MainV2Activity의 기존BottomNavigationView를 사용하며ChatMainFragment내부에 새로 만들지 않는다. - Figma의 iOS StatusBar는 Android 앱에서 별도 View로 만들지 않는다.
- API 스키마를 임의 변경하지 않는다.
- unread dot 표시는 이번 PRD 범위에 포함하지 않는다.
- 실시간 unread 갱신, WebSocket/SSE, pull-to-refresh, skeleton/shimmer는 이번 PRD 범위에 포함하지 않는다.
- Analytics/logging은 별도 요구가 없으므로 추가하지 않는다.
5. Target Users
- 메인 하단
대화탭에서 AI 채팅방과 DM 채팅방을 확인하려는 앱 사용자. kr.co.vividnext.sodalive.v2.main.chat영역을 구현/유지보수하는 Android 개발자.
6. User Stories
- 사용자는
대화탭을 눌렀을 때 최근 대화 목록을 바로 보고 싶다. - 사용자는
전체,AI 채팅,DM필터로 채팅방 유형을 빠르게 구분하고 싶다. - 사용자는 각 채팅방에서 상대 프로필, 이름, 마지막 메시지, 마지막 메시지 시간을 확인하고 싶다.
- 사용자는 새 대화를 시작하는 플로팅 버튼을 발견할 수 있어야 한다.
7. Core Features
Chat Tab Layout
ChatMainFragment의 루트 화면을 Figma 177:3466 기준으로 구성한다.
Requirements
- 전체 배경은
@color/black을 사용한다. - 상단에는 높이
60dp수준의 타이틀바를 배치하고 제목은대화로 표시한다. - 타이틀바 우측에는 기존
ic_bar_cash,ic_bar_search리소스를 재사용한다. 우측 상단의 action menu 이미지 사이 간격을 14dp로 지정하며, dimens에 정의되어 있는 값(@dimen/spacing_14)이 있으면 하드코딩 하지 않고 dimens의 값을 가져다가 설정한다. 타 화면 공통 컴포넌트 마크업 훼손을 피하기 위해 코드단(ChatMainFragment.kt내)에서 동적으로 LayoutParams 마진을 부여한다. - Figma의 하단 nav는
MainV2Activity의 기존BottomNavigationView가 담당하므로 Fragment layout에는 포함하지 않는다. - content 영역은 타이틀바 아래 필터 탭과 채팅방 목록으로 구성한다.
- 플로팅 액션 버튼은 우측 하단, 하단 내비게이션과 겹치지 않는 위치에 표시한다.
Edge Cases
- Android system bar inset은
MainV2Activity.overrideRootWindowInsets()의 기존 처리를 따른다. - 미니 플레이어가 표시될 수 있으므로 플로팅 버튼과 목록 하단 padding은
activity_main_v2.xml구조를 고려해 구현 계획에서 확정한다.
Chat Room Filter Tabs
채팅방 유형 필터를 제공한다.
Requirements
- 탭 메뉴는
전체,AI 채팅,DM순서로 표시한다. - Figma의 pill 형태와 가장 가까운 기존
CapsuleTabBarView를 우선 재사용한다. - selected 상태는 흰색 배경/검정 텍스트, normal 상태는 검정 배경/회색 stroke/흰색 텍스트로 표시되어야 한다.
- 기존
CapsuleTabBarView가 selected 텍스트 색상을 검정으로 처리하지 못하면 최소 범위로 위젯을 확장한다. - 현재 선택되어 있지 않은 탭을 터치하면 해당
filter값으로 첫 페이지 API를 호출한다. - 탭은 서버 목록 조회의 filter 역할을 담당한다.
- 첫 페이지 로딩 시 화면에 표시 중인 모든 기존 목록 데이터를 지우고 새 응답 데이터로 다시 세팅한다.
Edge Cases
- API 요청
filter는ALL,AI,DM중 하나만 사용한다. chatType은AI,DM문자열만 사용한다.- 탭 전환 후 표시할 item이 없으면 목록을 비운다. 별도 empty placeholder는 추가하지 않는다.
- 탭 전환 시점 또는 첫 번째 페이지 로드 시(isAppending = false인 상태) 목록의 스크롤을 최상단으로 이동시킨다.
Chat Room List
API 응답의 채팅방 목록을 Figma의 ChatList 형태로 표시한다.
Requirements
RecyclerView기반 세로 목록으로 구성한다.- 각 item은 프로필 이미지, 크리에이터/상대 이름, Direct badge, 마지막 메시지, 마지막 메시지 시간을 표시한다.
- 프로필 이미지는 원형
58dp수준으로 표시하고 기존 이미지 로딩 정책을 따른다. - 이름은
18spbold, 마지막 메시지는16spmedium, 시간은14spmedium 기준으로 맞춘다. - 마지막 메시지는 한 줄 말줄임 처리한다.
chatType이 DM이면 Figma의Directbadge를 표시하고, AI 채팅이면 표시하지 않는다.- unread dot은 표시하지 않는다.
- 채팅방 item 터치 시 extras는
roomId만 전달한다. chatType이AI이면 기존ChatRoomActivity로 이동한다.chatType이DM이면 이번 범위에서는 이동을 구현하지 않는다. DM 페이지 생성 및 연결은 다음 범위에서 진행한다.
Edge Cases
targetImageUrl이 비어 있거나 로딩 실패하면 기존 placeholder 정책을 따른다.targetName이 비어 있으면 빈 문자열 그대로 표시하고 별도 대체 문구는 추가하지 않는다.lastMessage가 비어 있으면 빈 줄이 아닌 한 줄 영역을 유지한다.lastMessageAt은 ISO-8601 문자열이다.lastMessageAt은 디바이스에 설정된 Timezone으로 변환한 뒤 화면 표시 문자열로 변환한다.- 일주일까지는 상대 시간으로 표시한다. 이 문구는 다국어 처리가 필요하다.
- 상대 시간으로 표시하지 않는 날짜는 언어별 날짜 포맷으로 표시한다.
- 일주일보다 오래되었고 올해 받은 메시지라면 아래 언어별 날짜 포맷을 적용한다.
- 한국어는
M월 d일형태로 표시한다. - 영어는
MMM d형태로 표시한다. - 일본어는
M月d日형태로 표시한다. - 올해가 2026년이고 2025년에 받은 메시지처럼 다른 연도 메시지라면
yyyy.MM.dd형태로 표시한다.
Floating Action Button
새 채팅 또는 대화 시작 진입점을 표시한다.
Requirements
- Figma 기준
soda/400계열 원형 버튼과 plus icon을 사용한다. - 기존
btn_plus_round,ic_plus_no_bg,btn_add등 사용 가능한 리소스를 먼저 확인해 재사용한다. - 적합한 기존 리소스가 없으면
v2전용 drawable을 최소 신규 추가한다. - 버튼 클릭 동작은 추후 결정한다.
Edge Cases
- 클릭 대상 화면이 확정되지 않았으므로 버튼은 표시하되 클릭 동작은 추가하지 않고 명시적 주석/계획 항목으로 남긴다.
API Integration
채팅방 목록 API를 호출하고 UI state로 변환한다.
API Endpoint
GET /api/v2/chat/rooms
Request Parameters
filter:ALL,AI,DM중 하나를 전달한다.cursor: 다음 페이지 요청 시 전달한다. 첫 페이지 요청 시에는 전달하지 않거나 서버 계약에 맞는 empty/null 값을 사용한다.
Response Model
현재 파일은 app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/data/HomeChatModels.kt이며, 기존 data class는 아래 이름을 사용한다.
data class ChatRoomListPageResponse(
val rooms: List<ChatRoomListItemResponse>,
val hasMore: Boolean,
val nextCursor: String?
)
data class ChatRoomListItemResponse(
val roomId: Long,
val chatType: String,
val targetName: String,
val targetImageUrl: String,
val lastMessage: String,
val lastMessageAt: String
)
Naming Requirements
HomeChatModels.kt는 채팅 탭 스펙과 맞지 않으므로ChatRoomModels.kt또는 동등하게 명확한 이름으로 변경하는 것을 권장한다.- DTO class 이름은 현재
ChatRoomListPageResponse,ChatRoomListItemResponse가 스펙에 적합하므로 유지 가능하다. - API 응답 최상위가 실제로
rooms,hasMore,nextCursor구조인지 구현 전 백엔드 계약 또는 샘플 응답으로 최종 확인한다.
Data Flow
ChatRoomApi->ChatRoomRepository->ChatMainViewModel->ChatMainFragment흐름을 사용한다.- Retrofit 응답은 기존 패턴처럼
Single<ApiResponse<ChatRoomListPageResponse>>를 우선 사용한다. - token 전달은 기존 v2 홈 API 패턴과
SharedPreferenceManager.token사용 방식을 따른다. - UI model은 DTO와 분리해
ChatRoomListUiItem같은 이름으로 둔다. - 첫 페이지 요청은 기존 목록을 clear한 뒤 응답 목록을 새로 세팅한다.
hasMore=true와nextCursor가 제공되면 스크롤 pagination으로 다음 페이지 API를 호출한다.- 다음 페이지 응답은 기존 목록 뒤에 append한다.
Edge Cases
- API success가 false이거나 data가 null이면 목록을 비우고 기존 Toast/error 정책을 따른다.
- 네트워크 오류가 발생해도 crash 없이 처리한다.
hasMore=false이거나nextCursor가 제공되지 않으면 추가 페이지를 호출하지 않는다.
8. UX / UI Expectations
- Figma 기준 화면 폭
402의 좌우 여백 감각을 Androidmatch_parent환경에서 유지한다. - 필터 탭은 높이
52dp, 좌측 여백은 기존CapsuleTabBarView의20dp또는 Figma14dp중 구현 계획에서 기존 위젯 일관성을 우선해 결정한다. - 채팅방 item은 세로 높이 약
86dp, 내부 padding14dp, 프로필과 텍스트 사이 간격14dp를 기준으로 한다. - Direct badge는
soda/400배경, radius4dp, Pattaya font 기반 텍스트를 우선 적용한다. - unread dot은 표시하지 않는다.
- 긴 이름과 긴 마지막 메시지는 겹치지 않고 말줄임 처리되어야 한다.
- 하단 내비게이션, 미니 플레이어, 플로팅 버튼이 서로 겹치지 않아야 한다.
9. Technical Constraints
- Android XML Views, ViewBinding, RecyclerView, RxJava3, Retrofit, Gson, Koin 구조를 따른다.
- 신규
ViewModel, API, Repository, adapter, custom view는kr.co.vividnext.sodalive.v2.main.chat하위에 작성한다. - 재사용 가능한 전용 위젯이 필요하면
kr.co.vividnext.sodalive.v2.widget하위에 작성하되, 단일 화면 전용이면v2.main.chat.ui하위에 둔다. - 기존
BaseFragment<FragmentV2MainChatBinding>구조를 유지한다. - 구현 전
docs/20260609_채팅_탭_페이지/plan-task.md를 작성하고, 그 문서에 따라 최소 구현한다. - 테스트는 mapper/formatter 단위 테스트와 Fragment layout/adapter 검증을 우선한다.
10. Widget Classification
| Figma 요소 | 기존 재사용 후보 | 판정 | 비고 |
|---|---|---|---|
| 하단 메인 nav | MainV2Activity + BottomNavigationView + menu_main_v2_bottom_navigation |
재사용 | Fragment 내부 구현 제외 |
| 타이틀바 우측 cash/search icon | ic_bar_cash, ic_bar_search |
재사용 | view_title_bar_default의 우측 액션 영역을 가변 아이콘 구조로 확장해 재사용한다 |
타이틀바 제목 대화 |
view_title_bar_default |
재사용 + 최소 확장 | 신규 chat 전용 title layout은 만들지 않는다 |
필터 탭 전체/AI 채팅/DM |
CapsuleTabBarView |
재사용 + 최소 확장 가능 | selected 텍스트 색상 검정 처리 필요 가능성 |
| 채팅방 리스트 item | 없음 | 신규 필요 | ChatRoomListItemView 또는 RecyclerView item XML/Adapter |
| Direct badge | 없음 | 신규 필요 | 작은 badge View/drawable, Pattaya font 재사용 |
| 프로필 원형 이미지 | 기존 Coil 로딩 패턴, drawable placeholder | 재사용 | 전용 위젯은 신규 item 내부에서 처리 |
| unread dot | 해당 없음 | 구현 제외 | 이번 PRD에서는 unread dot을 표시하지 않는다 |
| 플로팅 plus 버튼 | btn_plus_round, ic_plus_no_bg, btn_add 후보 |
부분 재사용/확인 필요 | 크기/색상이 맞지 않으면 신규 drawable 최소 추가 |
11. Metrics
ChatMainFragment진입 시GET /api/v2/chat/rooms가 한 번 호출된다.- API 응답의
rooms가RecyclerViewitem으로 표시된다. 전체,AI 채팅,DM탭 선택 시 각각ALL,AI,DMfilter로 첫 페이지 API가 호출된다.- 첫 페이지 로딩 시 기존 목록이 clear되고 새 응답 목록이 표시된다.
hasMore=true와nextCursor가 제공되면 스크롤 pagination이 동작한다.- DM item에만
Directbadge가 표시된다. - 마지막 메시지는 한 줄 말줄임 처리된다.
- unread dot은 표시되지 않는다.
lastMessageAt은 ISO-8601 문자열을 디바이스 Timezone 기준으로 변환한 뒤 일주일까지는 상대 시간으로 표시한다.- 상대 시간으로 표시하지 않는 올해 메시지의 날짜 포맷은 한국어
M월 d일, 영어MMM d, 일본어M月d日로 표시된다. - 상대 시간으로 표시하지 않는 다른 연도 메시지는
yyyy.MM.dd로 표시된다. - 채팅방 item 터치 시
chatType=AI는ChatRoomActivity로 이동하며 extras는roomId만 전달한다. chatType=DMitem 클릭 시 이동할 DM 페이지 생성 및 연결은 다음 범위에서 진행한다.- 플로팅 버튼 클릭 동작은 추후 결정 사항으로 남긴다.
- 빈 응답, API 실패, 이미지 실패가 crash 없이 처리된다.
- 하단 메인 내비게이션은 기존
MainV2Activity구조를 그대로 사용한다. - mapper/formatter 단위 테스트와 관련 layout/adapter 테스트가 통과한다.
12. Open Questions
- 플로팅 plus 버튼 클릭 시 이동해야 하는 기존 화면 또는 신규 flow는 추후 결정한다.
chatType=DMitem 클릭 시 이동할 신규 DM 페이지의 Activity/Fragment 이름과 생성 범위는 다음 범위에서 확정한다.- 상대 시간 표시의 다국어 string resource 문구는 구현 계획에서 확정한다.
13. References
- Figma: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=177-3466&m=dev
- 대상 Fragment:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt - 현재 layout:
app/src/main/res/layout/fragment_v2_main_chat.xml - 현재 DTO:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/data/HomeChatModels.kt - 기존 필터 탭 위젯:
app/src/main/java/kr/co/vividnext/sodalive/v2/widget/CapsuleTabBarView.kt - 하단 내비게이션:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
14. Verification Log
- 2026-06-09:
docs/prd/sample-prd.md,docs/agent-guides/work-plan-docs.md,docs/agent-guides/code-style.md를 확인해 PRD 구조와 저장소 문서 규칙을 확인했다. - 2026-06-09: Figma
177:3466의 design context와 screenshot을 확인해title-bar,tab-bar,ChatList,button-floating,nav구조를 분석했다. - 2026-06-09:
ChatMainFragment.kt,fragment_v2_main_chat.xml,HomeChatModels.kt,CapsuleTabBarView.kt,view_capsule_tab_bar.xml,activity_main_v2.xml,MainV2Activity.kt,view_title_bar_home.xml을 확인해 재사용/신규 위젯 후보를 분류했다. - 2026-06-09: 이번 단계는 PRD 작성만 수행했으며 구현/빌드/테스트는 실행하지 않았다.
- 2026-06-09: 사용자 추가 요구사항을 반영해 unread dot 미표시,
chatType/API filter/cursor,lastMessageAt표시 규칙, 탭 필터 재호출, pagination, 클릭 액션 범위를 보강했다. - 2026-06-09:
chatType=DMitem 클릭 이동은 다음 범위로 분리하고, 상대 시간으로 표시하지 않는 올해 메시지의 언어별 날짜 포맷을 한국어M월 d일, 영어MMM d, 일본어M月d日로 보강했다. - 2026-06-09: 사용자 피드백에 따라 채팅 전용
view_title_bar_chat.xml신규 생성을 제외하고, 기존view_title_bar_default.xml의 우측 아이콘 영역을 가변 개수로 확장해 재사용하는 방향으로 위젯 분류를 갱신했다. - 2026-06-10: Phase 6에서 채팅 탭 layout 골격과 CapsuleTab selected 색상 보정을 구현했다. 기존
view_title_bar_default.xml에ll_title_bar_actions를 추가해 우측 action icon을 가변으로 담을 수 있게 했고,fragment_v2_main_chat.xml은 blackConstraintLayoutroot 아래 title bar,view_capsule_tab_bar,rv_chat_rooms,btn_chat_floating만 포함하도록 구성했다. floating button은bg_chat_floating_button.xml의soda_400원형 배경과 기존ic_plus_no_bg를 사용하며 클릭 동작은 추가하지 않았다.CapsuleTabBarView는 PRD 요구대로 selected tab black text, normal tab white text를 사용하도록 수정했다. 검증으로ChatMainFragmentLayoutTest,CapsuleTabBarViewTest를 추가했고./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest",./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.*CapsuleTab*",./gradlew :app:mergeDebugResources,./gradlew :app:compileDebugKotlin,./gradlew :app:ktlintCheck가 모두BUILD SUCCESSFUL로 통과했다. Phase 7 범위인ChatMainFragment동작 연결, 실제 title/icon 세팅, filter listener, navigation/click action은 구현하지 않았다. - 2026-06-10: CapsuleTabBarView의 탭 전환 시 또는 첫 번째 페이지 로드 시(isAppending = false) RecyclerView의 스크롤을 최상단(position 0)으로 이동시키는 추가 요구사항을 수렴하고, 기존 PRD와 Plan-Task 문서를 갱신한 뒤 구현 및 검증을 완료했다.
- 2026-06-10: 채팅 타이틀바 우측 액션 메뉴 이미지 간의 간격을 14dp로 조정하는 요구사항을 반영했습니다.
dimens.xml에 정의된@dimen/spacing_14리소스를 사용하여 하드코딩 없이 안전하게ChatMainFragment.kt의 코드단에서 동적으로 LayoutParams를 통해marginStart값을 적용했습니다. 검증을 위한ChatMainFragmentLayoutTest.kt도 업데이트하고 테스트 통과를 완료했습니다. - 2026-06-10: Phase 10 통합 검증과 구현 제외 체크리스트 확인을 완료했습니다.
./gradlew :app:mergeDebugResources,./gradlew :app:compileDebugKotlin,./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*",./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.*CapsuleTab*",./gradlew :app:ktlintCheck가 모두BUILD SUCCESSFUL로 통과했습니다.ChatMainFragment는 DM item 클릭 이동과 floating button 이동을 구현하지 않고, fragment layout에는 하단BottomNavigationView, item layout에는 unread dot을 추가하지 않았으며, WebSocket/SSE, pull-to-refresh, skeleton/shimmer 및 API schema 필드명 임의 변경도 없음을 확인했습니다. ktlint의.editorconfig disabled_rulesdeprecation warning과 Gradle deprecation warning은 기존 경고로 남아 있습니다.