18 KiB
18 KiB
PRD: 피드 컴포넌트
1. Overview
Figma 63:4133, 63:4140, 63:4142, 63:4155 디자인을 기준으로 Rank, Live, Content, Community 4가지 Feed UI를 Android XML Views 기반 재사용 컴포넌트로 문서화한다.
2. Problem
- Feed UI는 같은 카드 배경과 radius를 공유하지만 variant별 내부 구조가 달라 화면별 개별 구현 시 간격, typography, 말줄임 정책이 달라질 수 있다.
- Rank Feed는 문장 안의 순위 텍스트만 강조색을 적용해야 하므로 일반 텍스트와 강조 범위를 분리한 바인딩 계약이 필요하다.
- Live Feed는 라이브 방송 이름과 상대 시간이 같은 줄에 있어 긴 제목이 시간을 침범하지 않도록 레이아웃 제약이 필요하다.
- Content와 Community Feed는 Figma 기준의 표시 요소가 다르므로 하나의 카드 컴포넌트 안에서 variant별 데이터 계약을 명확히 분리해야 한다.
3. Goals
- Figma 4개 노드 기준의 Feed variant를 제공한다.
- Rank: Figma
63:4133 - Live: Figma
63:4140 - Content: Figma
63:4142 - Community: Figma
63:4155
- Rank: Figma
- 공통 Feed 카드 배경, padding, radius, typography token을 Android resource 기준으로 정리한다.
- Rank Feed의 왼쪽 정사각형 영역은 실제 이미지를 표시하는
ImageView로 제공한다. - Rank Feed의 오른쪽 문구는 호출부가 변경 가능해야 하며, 순위에 해당하는 텍스트 범위만
soda_400강조색으로 표시한다. - Rank Feed 전체 UI는 세로 가운데 정렬을 유지한다.
- Live Feed의 라이브 방송 이름은 한 줄 제한과 끝 말줄임을 적용한다.
- Live Feed의 라이브 방송 이름은 상대 시간 텍스트와 겹치지 않도록 제목 영역을 시간 영역 앞에서 제약한다.
- Content와 Community Feed는 Figma에 표시된 이미지, 프로필, 제목/본문, 태그, 반응 수, 시간 정보를 variant별로 바인딩할 수 있게 한다.
- Feed를 가로 스크롤 목록에 배치하거나 한 줄에 여러 개 표시할 때는 Figma에 표시된 variant별 가로 사이즈를 사용한다.
- Feed를 한 줄에 1개만 표시할 때는 Figma 고정 폭이 아니라 부모 view의 가용 영역 폭을 채운다.
4. Non-Goals
- 이번 범위에서는 서버 API, DTO 필드명, pagination, 정렬, 추천 알고리즘을 변경하지 않는다.
- Feed 목록 화면 전체 개편이나 기존 화면 일괄 적용은 포함하지 않는다.
- Compose 컴포넌트 또는 Compose Theme를 추가하지 않는다.
- Figma에 없는 skeleton loading, shimmer, pressed animation, 추가 badge, 광고 영역은 추가하지 않는다.
- 이미지 로딩 라이브러리를 컴포넌트 내부에서 Coil/Glide 중 하나로 고정하지 않는다.
- Rank 순위 문구 생성 규칙은 컴포넌트 내부에서 계산하지 않고 호출부가 완성된 문장과 강조 범위를 전달한다.
5. Target Users
- 메인/알림/홈 화면에서 Feed 카드를 확인하는 앱 사용자.
- XML Views와 RecyclerView 또는 ViewBinding 기반으로 Feed UI를 구현/유지보수하는 Android 개발자.
6. User Stories
- 사용자는 랭킹, 라이브 종료, 콘텐츠 업로드, 커뮤니티 글 알림을 같은 Feed 카드 스타일 안에서 빠르게 구분하고 싶다.
- 사용자는 긴 라이브 제목이나 콘텐츠 제목이 있어도 카드 레이아웃이 깨지지 않기를 기대한다.
- 개발자는 4가지 Feed variant를 같은 컴포넌트 계약으로 재사용하고 싶다.
- 개발자는 Rank Feed 문구를 상황별로 바꾸면서 순위 텍스트만 강조하고 싶다.
7. Core Features
Feed Component
Feed 카드는 Rank, Live, Content, Community 4가지 variant를 가진다.
Figma References
- Rank Feed: 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=63-4133&m=dev
- Live Feed: 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=63-4140&m=dev
- Content Feed: 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=63-4142&m=dev
- Community Feed: 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=63-4155&m=dev
Common Token Requirements
- Root background는
gray_900(#202020)을 사용한다. - Root padding은
spacing_14(14dp)를 기준으로 한다. - Root radius는
radius_14(14dp)를 기준으로 한다. - 카드 내부 gap은 variant별 Figma 값에 맞춰
spacing_4,spacing_6,spacing_8,spacing_14,spacing_20을 우선 사용한다. - 모든 이미지 영역은 실제 이미지 URL을 바인딩할 수 있는
ImageView로 제공하고, 이미지 로딩 실패/placeholder 정책은 호출부가 결정한다. - 장식 아이콘은 접근성 노출이 필요하지 않으면
contentDescription=@null로 둔다.
Size Policy Requirements
- Feed root width는 사용 맥락에 따라 결정한다.
- 가로 스크롤 목록에서 사용하는 Feed item은 Figma에 표시된 variant별 root width를 사용한다.
- Grid 또는 flex row처럼 한 줄에 여러 Feed item을 표시하는 경우에도 각 item은 Figma에 표시된 variant별 root width를 사용한다.
- 한 줄에 Feed item을 1개만 표시하는 경우 root width는 부모 view의 가용 영역을 채운다.
- 부모 view의 가용 영역은 부모 width에서 부모 padding과 item decoration 간격을 제외한 폭이다.
- 한 줄 1개 모드에서도 내부 image size, padding, radius, typography, icon size는 Figma 기준값을 유지한다.
- 한 줄 1개 모드에서 늘어나는 영역은 텍스트 column, 본문 영역, 제목 영역처럼 남은 폭을 사용하는 content 영역이다.
- 구현에서는
WrapContent또는 Figma fixed width 모드와MatchParent또는 parent available width 모드를 명시적으로 구분해야 한다.
Variant Requirements
| Variant | Figma node | Figma root width | Layout | Main elements |
|---|---|---|---|---|
Rank |
63:4133 |
374dp |
가로 배치, 세로 가운데 정렬 | 80dp 정사각형 이미지, 순위 overlay, 변경 가능한 문장 |
Live |
63:4140 |
374dp |
세로 배치 | 프로필/종료 문구 row, 라이브 제목/상대 시간 row |
Content |
63:4142 |
374dp |
가로 배치 | 122dp 콘텐츠 이미지, 프로필, 콘텐츠명, 카테고리 태그, 상대 시간 |
Community |
63:4155 |
374dp |
세로 배치 | 프로필 row, 커뮤니티 본문, 키워드, 댓글/좋아요 반응 수 |
Rank Feed Requirements
- 왼쪽 정사각형은 실제 이미지를 표시하는
ImageView다. - 이미지 영역은
80dp x 80dp,radius_14,centerCrop을 기준으로 한다. - 이미지 위에는 Figma
Rank(type=Default, rank1=all)기준의 순위 숫자 overlay를 표시한다. - 순위 숫자 overlay는
Pattaya Regular,40sp, white-to-#EEEEEE계열 표현에 가장 가깝게 표시하고, 그림자는0dp 0dp 4dp rgba(0,0,0,0.48)기준으로 둔다. - 순위 숫자 overlay는 이미지 오른쪽 하단에 배치하되 Figma처럼 이미지 영역을 약간 벗어나 보일 수 있도록 image container와 root는 해당 overlay를 자르지 않는다.
- 오른쪽 텍스트는 호출부가 변경 가능한 문자열이어야 한다.
- 오른쪽 텍스트의 기본 style은
Pretendard Variable Medium,16sp, line-height1.45, white다. 1위,10위,30위처럼 순위를 나타내는 텍스트 범위만soda_400(#00BDF7)로 표시한다.- 순위 강조 범위는 컴포넌트가 문자열에서 추론하지 않고 호출부가
highlightRanges또는 동등한 계약으로 전달한다. - 전체 root는
items_center에 해당하는 세로 가운데 정렬을 유지한다.
Live Feed Requirements
- 첫 번째 row는
20dp원형 프로필 이미지와 서버/호출부가 전달한 종료 안내 문구를 하나의12spTextView로 표시한다. - 종료 안내 문구는 creator name과 suffix를 컴포넌트 내부에서 조합하지 않고, 서버/호출부가 내려준 완성 문자열을 그대로 표시한다.
- 종료 안내 문구 TextView는 폭 제약을 받아 한 줄 제한과 끝 말줄임을 적용하며 카드 바깥으로 넘치지 않아야 한다.
- 두 번째 row는 라이브 방송 이름과 상대 시간을 표시한다.
- 라이브 방송 이름은
Typography.Heading4또는18sp bold, white,maxLines=1,ellipsize=end를 적용한다. - 상대 시간은
Typography.Body6또는14sp regular,gray_500, 오른쪽 정렬을 기준으로 한다. - 라이브 방송 이름과 상대 시간 사이에는 최소
20dpgap을 둔다. - 라이브 방송 이름 영역은 상대 시간 영역 앞에서 폭이 제한되어야 하며, 긴 제목이
2분 전,10분 전같은 시간 텍스트와 겹치면 안 된다.
Content Feed Requirements
- 왼쪽 콘텐츠 이미지는 category tag에 따라 높이가 달라진다.
- 왼쪽 콘텐츠 이미지의 가로 크기는 현재 설정된 값을 유지하고, category별 비율에 따라 높이만 계산한다.
- category tag에 들어갈 수 있는 값은
콘텐츠,시리즈,매거진이다. 콘텐츠category는1:1비율로 표시한다.시리즈category는163:230비율로 표시한다.매거진category는163:218비율로 표시한다.- category별 이미지 높이 계산은
height = imageWidth * ratioHeight / ratioWidth를 기준으로 한다. - 이미지 영역은 모든 category에서
radius_14,centerCrop을 기준으로 한다. - 오른쪽 column은 프로필 row, 콘텐츠명, 하단 메타 row를 가진다.
- 프로필 row는
20dp원형 프로필 이미지와12spcreator name을 표시한다. - 콘텐츠명은
Typography.Heading4또는18sp bold, white,maxLines=1,ellipsize=end를 적용한다. - 하단 메타 row는 왼쪽 category tag와 오른쪽 상대 시간을 표시한다.
- category tag는
gray_700background,radius_4, horizontal padding4dp, vertical padding2dp, text14sp regular,gray_100을 기준으로 한다. - 기본 category label은
콘텐츠이며, 표시 문자열은Content,Series,Magazine별 string resource를 사용한다. - 상대 시간은
14sp regular,gray_500, 오른쪽 정렬을 기준으로 한다.
Community Feed Requirements
- 상단 profile row는
42dp원형 프로필 이미지, creator name, 상대 시간을 표시한다. - creator name은
Typography.Body5또는14sp medium, white를 기준으로 한다. - 상대 시간은
Typography.Body6또는14sp regular,gray_500을 기준으로 한다. - 본문은
Typography.Body3또는16sp regular, white, line-height1.45를 기준으로 한다. - 본문 영역 폭은 카드 내부 폭을 채우며, Figma 기준
346dp를 기준값으로 삼되 부모 폭이 달라지면 내부 padding을 제외한 가용 폭을 사용한다. - 키워드 영역은
Typography.Body3또는16sp regular,soda_400을 기준으로 한다. - 반응 row는 댓글 수와 좋아요 수를 표시하고, icon size는
18dp, 텍스트는16sp regular,gray_500을 기준으로 한다. - 댓글 아이콘은
ic_feed_community_reply, 좋아요 아이콘은ic_feed_community_heart리소스를 사용한다. - 본문과 키워드가 비어 있으면 전달된 값 그대로 표시하되, 호출부 정책에 따라 빈 row를 숨길 수 있는 API를 제공한다.
Data Contract Requirements
- 모든 Feed item은
feedId와variant를 포함해야 한다. - 시간 표시가 있는
Live,Content,Communityitem은createdAtText를 포함해야 한다. - Rank 데이터는 다음 정보를 포함해야 한다.
imageUrl: 왼쪽 정사각형 이미지 URL.rankText: 이미지 overlay 또는 접근성에 사용할 순위 문자열.message: 오른쪽 전체 문장.highlightRanges: 순위 텍스트만 강조할 문자열 범위 목록.
- Live 데이터는 다음 정보를 포함해야 한다.
creatorId,creatorName,creatorImageUrl.liveId,liveTitle.endedMessage: 서버/호출부가 내려준 완성된 라이브 종료 안내 문구.
- Content 데이터는 다음 정보를 포함해야 한다.
creatorId,creatorName,creatorImageUrl.contentId,contentTitle,contentImageUrl.category:Content,Series,Magazine중 하나. 표시 label은 각각콘텐츠,시리즈,매거진이다.
- Community 데이터는 다음 정보를 포함해야 한다.
creatorId,creatorName,creatorImageUrl.postId,bodyText,keywordText.commentCount,likeCount.
Edge Cases
- 이미지 URL이 비어 있으면 호출부 이미지 로딩 정책에 따른 placeholder 또는 빈 이미지를 표시한다.
- Rank
message가 비어 있으면 오른쪽 텍스트 영역은 빈 문자열로 유지한다. - Rank
highlightRanges가 문자열 범위를 벗어나면 구현 단계에서 안전하게 무시하거나 clamp하는 정책을 단위 테스트로 고정한다. - Live
liveTitle이 매우 길면 한 줄 말줄임 처리하고 상대 시간은 항상 보존한다. - 상대 시간 문자열이 길어도 한 줄로 표시하며, 제목/본문 영역이 해당 영역을 침범하지 않는다.
- Content
contentTitle이 길면 한 줄 말줄임 처리한다. - Content
category가 서버/호출부에서 누락되면 기본값은Content로 간주해콘텐츠label과1:1이미지 비율을 적용한다. - Community 본문이 길면 Figma처럼 여러 줄 표시를 허용하되, 최대 줄 수 제한은 호출 화면 정책에 따른다.
- 댓글 수 또는 좋아요 수가 없으면 0으로 표시하거나 호출부가 전달한 문자열을 그대로 표시하는 정책 중 하나를 구현 계획에서 고정한다.
8. UX / UI Expectations
- 4가지 Feed 모두 어두운 배경 위에서 사용하는 것을 전제로 한다.
- Root 카드의 배경, radius, padding은 variant 간 일관성을 유지한다.
- 가로 스크롤 또는 한 줄 다중 표시에서는 Figma
374dproot width를 유지한다. - 한 줄 1개 표시에서는 부모 view의 가용 폭을 채우되 내부 고정 요소 크기는 유지한다.
- Rank Feed는 이미지와 텍스트가 한 줄 카드 안에서 세로 가운데 정렬되어야 한다.
- Live Feed의 제목은 시간과 겹치지 않고, 시간이 항상 오른쪽에서 읽혀야 한다.
- Content Feed는 왼쪽 큰 썸네일과 오른쪽 정보 column의 수직 배치가 Figma와 일치해야 한다.
- Content Feed의 왼쪽 이미지는 category가 바뀌어도 가로 크기를 유지하고,
콘텐츠,시리즈,매거진비율에 따라 높이만 변경되어야 한다. - Community Feed는 프로필, 본문, 키워드, 반응 row의 세로 흐름이 유지되어야 한다.
- 모든 터치 동작은 컴포넌트 내부에서 목적지를 결정하지 않고 호출부 callback으로 위임한다.
9. Technical Constraints
- 현재 프로젝트는 Android XML Views + Kotlin custom View + ViewBinding/resource 기반이므로 XML layout과 Kotlin custom view 패턴을 우선한다.
- 신규 Kotlin 코드는
app/src/main/java/kr/co/vividnext/sodalive/v2하위 패키지에 작성한다. - 재사용 가능한 위젯은
kr.co.vividnext.sodalive.v2.widget.feed하위에 둔다. - 색상, spacing, radius, typography는 기존
colors.xml,dimens.xml,typography.xmltoken을 우선 재사용한다. - 기존
AudioContentCardView,LiveThumbnail*View,CreatorRanking*View,ContentRanking*View패턴처럼 표시 데이터 contract와 custom view 바인딩을 분리한다. - 이미지 로딩은 컴포넌트 내부에 고정하지 않고
ImageView를 노출하거나 adapter/caller가 수행한다. - 기존 화면 파일은 요청 없이 변경하지 않는다.
10. Metrics
- Rank, Live, Content, Community 4개 variant가 Figma 노드와 주요 배치, 색상, typography, spacing 계약을 만족한다.
- Rank Feed의 왼쪽 영역은 실제 이미지로 바인딩 가능하다.
- Rank Feed의 오른쪽 문구는 호출부에서 변경 가능하고, 순위 텍스트 범위만
soda_400으로 표시된다. - Rank Feed root는 세로 가운데 정렬을 유지한다.
- Live Feed의 라이브 방송 이름은
maxLines=1,ellipsize=end이며 상대 시간과 겹치지 않는다. - Content Feed의 콘텐츠명은 한 줄 말줄임 처리된다.
- Content Feed의 왼쪽 이미지는
콘텐츠=1:1,시리즈=163:230,매거진=163:218비율을 만족한다. - Community Feed의 댓글/좋아요 반응 수가 Figma 순서와 스타일로 표시된다.
- 가로 스크롤 및 한 줄 다중 표시에서는 Feed root width가 Figma 기준
374dp로 적용된다. - 한 줄 1개 표시에서는 Feed root width가 부모 view의 가용 영역 폭으로 적용된다.
- 관련 unit test와 Android resource merge/build가 성공한다.
11. Open Questions
- 사용자 요청이 “문서만 작성”이므로 이번 작업에서는 구현 파일을 만들지 않는다.
- Content와 Community variant는 별도 추가 조건이 없으므로 Figma
get_design_context와 screenshot 기준으로 요구사항을 확정한다. - 실제 적용 화면과 API/DTO 매핑은 구현 단계 또는 호출부 작업에서 결정한다.
- Community 본문 최대 줄 수는 Figma에서 긴 본문이 여러 줄로 표시되어 있으므로 컴포넌트 기본값은 제한하지 않고, 호출 화면 정책으로 제한할 수 있게 둔다.