Files
sodalive-android/docs/prd/20260521_피드컴포넌트_prd.md

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
  • 공통 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

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-height 1.45, white다.
  • 1위, 10위, 30위처럼 순위를 나타내는 텍스트 범위만 soda_400 (#00BDF7)로 표시한다.
  • 순위 강조 범위는 컴포넌트가 문자열에서 추론하지 않고 호출부가 highlightRanges 또는 동등한 계약으로 전달한다.
  • 전체 root는 items_center에 해당하는 세로 가운데 정렬을 유지한다.

Live Feed Requirements

  • 첫 번째 row는 20dp 원형 프로필 이미지와 서버/호출부가 전달한 종료 안내 문구를 하나의 12sp TextView로 표시한다.
  • 종료 안내 문구는 creator name과 suffix를 컴포넌트 내부에서 조합하지 않고, 서버/호출부가 내려준 완성 문자열을 그대로 표시한다.
  • 종료 안내 문구 TextView는 폭 제약을 받아 한 줄 제한과 끝 말줄임을 적용하며 카드 바깥으로 넘치지 않아야 한다.
  • 두 번째 row는 라이브 방송 이름과 상대 시간을 표시한다.
  • 라이브 방송 이름은 Typography.Heading4 또는 18sp bold, white, maxLines=1, ellipsize=end를 적용한다.
  • 상대 시간은 Typography.Body6 또는 14sp regular, gray_500, 오른쪽 정렬을 기준으로 한다.
  • 라이브 방송 이름과 상대 시간 사이에는 최소 20dp gap을 둔다.
  • 라이브 방송 이름 영역은 상대 시간 영역 앞에서 폭이 제한되어야 하며, 긴 제목이 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 원형 프로필 이미지와 12sp creator name을 표시한다.
  • 콘텐츠명은 Typography.Heading4 또는 18sp bold, white, maxLines=1, ellipsize=end를 적용한다.
  • 하단 메타 row는 왼쪽 category tag와 오른쪽 상대 시간을 표시한다.
  • category tag는 gray_700 background, radius_4, horizontal padding 4dp, vertical padding 2dp, text 14sp 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-height 1.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은 feedIdvariant를 포함해야 한다.
  • 시간 표시가 있는 Live, Content, Community item은 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 374dp root 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.xml token을 우선 재사용한다.
  • 기존 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에서 긴 본문이 여러 줄로 표시되어 있으므로 컴포넌트 기본값은 제한하지 않고, 호출 화면 정책으로 제한할 수 있게 둔다.