feat(widget): 오디오 콘텐츠 태그 배지를 추가한다

This commit is contained in:
2026-05-27 14:50:59 +09:00
parent a8e0f2377d
commit 799dd7fc92
14 changed files with 693 additions and 6 deletions

View File

@@ -0,0 +1,239 @@
# 오디오 콘텐츠 위젯 태그 배지 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Figma `20:3840`, `20:3843`, `20:3815`, `20:3814` 기준으로 v2 패키지 아래 신규 위젯인 `AudioContentCardView` 썸네일 영역에 Original, First, Point, Free 태그 배지를 추가한다.
**Architecture:** v2 신규 위젯인 `AudioContentCardView``view_audio_content_card.xml`만 수정해 썸네일 `ImageView`를 overlay 가능한 container 안으로 이동하고 상단/하단 tag row를 추가한다. 태그 타입과 정렬 순서는 순수 Kotlin contract로 분리해 단위 테스트하고, 실제 이미지 로딩은 기존 `thumbnailView()` API로 계속 호출부에 위임한다. 레거시 화면, 레거시 adapter, 레거시 XML에는 포함하지 않는다.
**Tech Stack:** Android XML Views, Kotlin custom View, Android resources, JUnit4 local unit test.
---
## 작업 목표
- 대상은 v2 신규 위젯 파일인 `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt``app/src/main/res/layout/view_audio_content_card.xml`로 한정한다.
- 레거시 화면과 기존 화면 적용 작업은 제외한다.
- Original, First는 썸네일 왼쪽 상단에 표시한다.
- Original과 First가 함께 있으면 Original, First 순서로 표시한다.
- Point, Free는 썸네일 왼쪽 하단에 표시한다.
- Free 태그는 string resource 기반 다국어 텍스트로 생성한다.
- 기존 `setContent`, `thumbnailView`, `setSize` API는 유지한다.
- 구현 중 체크박스와 검증 기록은 이 문서에 누적한다.
## 파일 구조
- Read: `docs/prd/20260522_오디오콘텐츠위젯태그배지_prd.md`
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt`
- tag view 참조, `setTags(...)`, 상/하단 정렬 적용, size 적용 시 thumbnail container 크기 갱신을 처리한다.
- Modify: `app/src/main/res/layout/view_audio_content_card.xml`
- thumbnail overlay container와 top/bottom tag row를 추가한다.
- Do not modify: 레거시 화면 XML, 레거시 adapter, 기존 API/DTO, 기존 화면 바인딩 코드
- 신규 위젯 자체 기능만 추가한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTag.kt`
- `Original`, `First`, `Point`, `Free` 태그 타입과 위치/정렬 계약을 정의한다.
- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTagTest.kt`
- 태그 중복 제거, 상단/하단 분류, 고정 정렬 순서를 검증한다.
- Modify: `app/src/main/res/values/strings.xml`
- `audio_content_tag_free` 문자열을 추가한다.
- Modify: `app/src/main/res/values-en/strings.xml`
- `audio_content_tag_free` 영어 문자열을 추가한다.
- Modify: `app/src/main/res/values-ja/strings.xml`
- `audio_content_tag_free` 일본어 문자열을 추가한다.
- Add if missing: `app/src/main/res/drawable/ic_content_tag_original.png`
- Add if missing: `app/src/main/res/drawable/ic_content_tag_point.png`
- Add if missing: `app/src/main/res/drawable/ic_content_tag_first_star.png`
- Create if missing: `app/src/main/res/drawable/bg_audio_content_tag_first.xml`
- `#FF34B8` solid background for the `62dp x 24dp` First badge.
- Create if missing: `app/src/main/res/drawable/bg_audio_content_tag_free.xml`
- `#052742` solid background for the Free badge. The view uses height `24dp`, min width `34dp`, and `wrap_content` width.
- Modify: `docs/plan-task/20260522_오디오콘텐츠위젯태그배지.md`
- 구현 중 체크박스와 검증 기록을 누적한다.
## 구현 계획
### Task 1: 기존 위젯과 Figma 기준 확인
**Files:**
- Read: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt`
- Read: `app/src/main/res/layout/view_audio_content_card.xml`
- Read: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardSize.kt`
- Read: `docs/prd/20260519_오디오콘텐츠카드컴포넌트_prd.md`
- Read: `docs/prd/20260522_오디오콘텐츠위젯태그배지_prd.md`
- [x] **Step 1: 대상 위젯 확인**
Run: `rg -n "class AudioContentCardView|iv_audio_content_thumbnail|ll_audio_content_label|AudioContentCardSize|thumbnailView" app/src/main/java/kr/co/vividnext/sodalive/v2/widget app/src/main/res/layout/view_audio_content_card.xml`
Expected: 변경 대상이 v2 신규 위젯인 `AudioContentCardView.kt``view_audio_content_card.xml`이며, 기존 size/content/thumbnail API를 유지해야 함을 확인한다. 레거시 화면 파일은 변경 대상에서 제외한다.
- [ ] **Step 2: Figma tag 기준 확인**
Run tools:
- `Figma_get_design_context(20:3840)`
- `Figma_get_screenshot(20:3840)`
- `Figma_get_design_context(20:3843)`
- `Figma_get_screenshot(20:3843)`
- `Figma_get_design_context(20:3815)`
- `Figma_get_screenshot(20:3815)`
- `Figma_get_design_context(20:3814)`
- `Figma_get_screenshot(20:3814)`
Expected: First, Free 단일 tag와 Free/Point + Original/First 조합의 위치, 크기, 색상, 순서를 확인한다.
- [x] **Step 3: 기존 리소스 확인**
Run: `rg -n "ic_content_tag_original|ic_content_tag_point|ic_content_tag_first_star|audio_content_tag_free|#ff34b8|#052742" app/src/main/res`
Expected: 재사용 가능한 리소스가 있으면 재사용하고, 없으면 최소 신규 리소스를 추가한다.
### Task 2: AudioContentTag contract TDD
**Files:**
- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTagTest.kt`
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTag.kt`
- [x] **Step 1: RED - 태그 정렬 테스트 추가**
Test cases:
- 전달 순서가 `First`, `Original`이어도 top row는 `Original`, `First`가 된다.
- 전달 순서가 `Free`, `Point`이어도 bottom row는 `Point`, `Free`가 된다.
- 중복 태그는 한 번만 표시된다.
- 빈 set은 top/bottom 모두 비어 있다.
- [x] **Step 2: RED 실행**
Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.AudioContentTagTest"`
Expected: `Unresolved reference 'AudioContentTag'` 또는 `Unresolved reference 'audioContentTopTags'` / `Unresolved reference 'audioContentBottomTags'`로 실패한다.
- [x] **Step 3: GREEN - 최소 contract 추가**
Implementation notes:
- `enum class AudioContentTag { Original, First, Point, Free }`를 추가한다.
- `fun Collection<AudioContentTag>.audioContentTopTags(): List<AudioContentTag>``[Original, First]` 순서를 적용한다.
- `fun Collection<AudioContentTag>.audioContentBottomTags(): List<AudioContentTag>``[Point, Free]` 순서를 적용한다.
- [x] **Step 4: GREEN 실행**
Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.AudioContentTagTest"`
Expected: `BUILD SUCCESSFUL`
### Task 3: XML overlay 구조와 리소스 추가
**Files:**
- Modify: `app/src/main/res/layout/view_audio_content_card.xml`
- Modify: `app/src/main/res/values/strings.xml`
- Modify: `app/src/main/res/values-en/strings.xml`
- Modify: `app/src/main/res/values-ja/strings.xml`
- Add if missing: drawable resources for tags/backgrounds
- [x] **Step 1: thumbnail overlay container 적용**
Implementation notes:
- 기존 `iv_audio_content_thumbnail``FrameLayout` overlay container 내부로 이동한다.
- container id는 예: `fl_audio_content_thumbnail_container`로 둔다.
- `AudioContentCardView.setSize`는 thumbnail `ImageView`와 container 모두 `thumbnailSizeDp` 크기로 맞춘다.
- 기존 `thumbnailView()`는 동일한 `ImageView`를 반환한다.
- [x] **Step 2: top/bottom tag row 추가**
Implementation notes:
- top row id 예: `ll_audio_content_tag_top`
- bottom row id 예: `ll_audio_content_tag_bottom`
- top row: parent start/top 정렬, horizontal orientation
- bottom row: parent start/bottom 정렬, horizontal orientation
- row는 태그가 없을 때 `gone` 처리한다.
- [x] **Step 3: Free 다국어 문자열 추가**
Suggested strings:
- `values/strings.xml`: `무료`
- `values-en/strings.xml`: `Free`
- `values-ja/strings.xml`: `無料`
- [x] **Step 4: resource merge 확인**
Run: `./gradlew :app:assembleDebug`
Expected: Android resource merge와 debug assemble이 성공한다.
### Task 4: AudioContentCardView tag binding 구현
**Files:**
- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt`
- [x] **Step 1: view 참조 추가**
Implementation notes:
- thumbnail container, top tag row, bottom tag row를 `onFinishInflate()`에서 찾는다.
- `setSize()`에서 thumbnail container와 thumbnail image 크기를 함께 갱신한다.
- [x] **Step 2: public tag API 추가**
Implementation notes:
- `fun setTags(tags: Set<AudioContentTag>)` API를 추가한다.
- top row는 `tags.audioContentTopTags()` 결과를 렌더링한다.
- bottom row는 `tags.audioContentBottomTags()` 결과를 렌더링한다.
- 빈 row는 `View.GONE`, 표시 row는 `View.VISIBLE`로 처리한다.
- 기존 public API는 제거하거나 시그니처 변경하지 않는다.
- [x] **Step 3: tag view 생성 로직 추가**
Implementation notes:
- Original: `ImageView` + `ic_content_tag_original`, `24dp x 24dp`
- Point: `ImageView` + `ic_content_tag_point`, `24dp x 24dp`
- First: `LinearLayout` + `bg_audio_content_tag_first` + `ic_content_tag_first_star` + `FIRST` 텍스트, `62dp x 24dp`
- star icon: `17dp x 17dp`, `marginStart=2dp`, `top=4dp`, `contentDescription=null`
- text: `Phosphate Solid`, `16sp`, white, `singleLine=true`, `includeFontPadding=false`, `marginStart=1dp`, `top=2dp`
- Free: `TextView` + `bg_audio_content_tag_free` + `@string/audio_content_tag_free`, height `24dp`, min width `34dp`, width `wrap_content`
- 장식 아이콘의 `contentDescription``null`로 둔다.
### Task 5: 검증
**Files:**
- All changed implementation/resources/tests
- Modify: `docs/plan-task/20260522_오디오콘텐츠위젯태그배지.md`
- [x] **Step 1: 단위 테스트 실행**
Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.AudioContentTagTest"`
Expected: `BUILD SUCCESSFUL`
- [x] **Step 2: 리소스/빌드 검증 실행**
Run: `./gradlew :app:assembleDebug`
Expected: `BUILD SUCCESSFUL`
- [x] **Step 3: LSP diagnostics 확인**
Run tools:
- `lsp_diagnostics(app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt)`
- `lsp_diagnostics(app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTag.kt)`
- `lsp_diagnostics(app/src/test/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTagTest.kt)`
Expected: 변경 파일에 신규 error가 없다.
- [x] **Step 4: 검증 기록 누적**
이 문서 하단에 무엇/왜/어떻게, 실행 명령, 결과, 남은 이슈를 한국어로 누적한다.
- [x] **Step 5: 레거시 변경 없음 확인**
Run: `git diff --name-only | rg -v "^(docs/prd/20260522_오디오콘텐츠위젯태그배지_prd.md|docs/plan-task/20260522_오디오콘텐츠위젯태그배지.md|app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt|app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTag.kt|app/src/test/java/kr/co/vividnext/sodalive/v2/widget/AudioContentTagTest.kt|app/src/main/res/layout/view_audio_content_card.xml|app/src/main/res/values/strings.xml|app/src/main/res/values-en/strings.xml|app/src/main/res/values-ja/strings.xml|app/src/main/res/drawable/)"`
Expected: 출력이 없어야 한다. 출력이 있으면 레거시 화면 또는 범위 밖 파일을 변경한 것이므로 되돌리거나 계획을 갱신하기 전에 사용자 확인을 받는다.
## 검증 기록
### 2026-05-22 문서 생성
- 무엇/왜/어떻게: 사용자 요청에 따라 오디오 콘텐츠 위젯 태그 배지 추가 작업의 PRD와 구현 계획/TASK 문서를 작성했다. 대상 위젯은 v2 패키지 아래 신규 위젯인 `AudioContentCardView.kt``view_audio_content_card.xml`로 한정했고, Figma `20:3840`, `20:3843`, `20:3815`, `20:3814` 기준 태그 종류/위치/정렬/다국어 요구사항을 반영했다. 레거시 화면에는 포함하지 않는다는 제약도 문서에 반영했다.
- 실행 명령: 문서 작성만 수행했으므로 Gradle 빌드는 실행하지 않았다.
- 결과: 구현 전 기준 문서가 준비되었다.
### 2026-05-27 구현 및 검증
- 무엇/왜/어떻게: `AudioContentCardView` 썸네일을 `FrameLayout` overlay container로 감싸고 top/bottom tag row를 추가했다. `AudioContentTag` enum과 `audioContentTopTags()` / `audioContentBottomTags()` 순수 Kotlin contract를 추가해 Original/First, Point/Free 정렬과 중복 제거를 테스트했다. `setTags(tags: Set<AudioContentTag>)` API를 추가해 Original, First, Point, Free 태그를 썸네일 내부에 렌더링하도록 구현했으며, 기존 `setContent`, `setSize`, `thumbnailView` API는 유지했다. Free 태그 문자열과 First/Free 배경 drawable도 추가했다.
- 실행 명령: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.AudioContentTagTest"`를 RED/GREEN으로 실행했고, `./gradlew :app:assembleDebug`를 실행했다. `git diff --name-only`와 계획 문서의 범위 확인 명령으로 레거시 변경 여부를 확인했다.
- 결과: RED 단계는 `Unresolved reference 'AudioContentTag'``Unresolved reference 'audioContentTopTags'` / `audioContentBottomTags` 컴파일 실패로 확인했다. GREEN 단계의 targeted unit test와 `assembleDebug`는 모두 `BUILD SUCCESSFUL`로 통과했다. debug APK 산출물 `app/build/outputs/apk/debug/app-debug.apk`도 생성되었다.
- 남은 이슈: Figma MCP `Figma_get_design_context` 호출은 timeout으로 완료하지 못해, 태그 크기/위치/색상은 PRD와 이 계획 문서에 이미 정리된 Figma 기준 및 기존 `SeriesContentCardView` 패턴을 근거로 구현했다. Kotlin LSP는 `kotlin-lsp` 미설치로 diagnostics를 실행할 수 없었고, Gradle compile/test/build로 대체 검증했다.

View File

@@ -0,0 +1,138 @@
# PRD: 오디오 콘텐츠 위젯 태그 배지
## 1. Overview
Figma `20:3840`, `20:3843`, `20:3815`, `20:3814` 디자인을 기준으로 v2 패키지 아래에 생성된 신규 오디오 콘텐츠 위젯(`AudioContentCardView`, `view_audio_content_card.xml`)의 썸네일 영역에 콘텐츠 태그 배지를 표시한다.
---
## 2. Problem
- 기존 `AudioContentCardView`는 썸네일, 제목, 크리에이터명만 제공해 콘텐츠의 구매/원작/선공개 속성을 카드에서 즉시 구분할 수 없다.
- 기존 오디오 콘텐츠 카드 PRD에서는 태그 배지를 명시적으로 제외했으므로, 이번 추가 요구사항을 별도 문서로 확정해야 한다.
- 태그별 위치와 표시 순서가 정해져 있어 호출부마다 임의 구현하면 Original/First, Point/Free 정렬이 달라질 수 있다.
- Free 태그는 이미지 리소스가 아니라 다국어 문자열로 생성해야 하므로 string resource 기반 계약이 필요하다.
---
## 3. Goals
- `AudioContentCardView` 썸네일 위에 Original, First, Point, Free 태그 배지를 표시할 수 있게 한다.
- 대상 구현 파일은 v2 패키지 아래 신규 위젯으로 생성된 다음 파일로 한정한다.
- `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt`
- `app/src/main/res/layout/view_audio_content_card.xml`
- 왼쪽 상단에는 Original, First를 표시하고, 둘 다 있으면 Original 다음 First 순서를 유지한다.
- 왼쪽 하단에는 Point, Free를 표시한다.
- Original, Point는 제공된 drawable 리소스를 사용한다.
- First는 `ic_content_tag_first_star` 리소스를 사용해 Figma `20:3840` 형태의 배지를 생성한다.
- Free는 Figma `20:3843` 기준으로 다국어 string resource 텍스트를 사용해 생성한다.
- Figma 예시 조합을 지원한다.
- Free + Original + First: `20:3815`
- Point + Original + First: `20:3814`
---
## 4. Non-Goals
- 피드 위젯(`kr.co.vividnext.sodalive.v2.widget.feed.*`)에는 이번 변경을 적용하지 않는다.
- 레거시 화면, 레거시 adapter, 레거시 XML에는 이번 변경을 포함하지 않는다.
- 기존 화면, adapter, API, DTO에 태그 값을 일괄 연결하지 않는다.
- 썸네일 이미지 로딩 정책이나 placeholder 정책을 변경하지 않는다.
- 오디오 콘텐츠 카드의 기존 size contract(`large`, `medium`, `small`)를 변경하지 않는다.
- Compose 컴포넌트 또는 신규 Activity/Fragment/ViewModel을 만들지 않는다.
- Figma에 없는 추가 badge, animation, dim overlay, pressed effect는 추가하지 않는다.
---
## 5. Target Users
- v2 오디오 콘텐츠 카드에서 콘텐츠 상태를 빠르게 구분하려는 앱 사용자.
- `AudioContentCardView`를 XML Views 기반 목록, 캐러셀, 그리드에서 재사용하는 Android 개발자.
---
## 6. User Stories
- 사용자는 콘텐츠 썸네일에서 Original, First, Point, Free 속성을 즉시 확인하고 싶다.
- 개발자는 오디오 콘텐츠 카드에 표시할 태그 목록만 전달하고, 카드 내부에서 Figma 기준 위치와 순서가 자동으로 적용되기를 원한다.
- 개발자는 Free 태그 문구가 한국어/영어/일본어 등 기존 다국어 정책을 따르기를 원한다.
---
## 7. Core Features
### Audio Content Card Tag Badges
v2 신규 위젯인 `AudioContentCardView`의 썸네일 영역을 overlay 가능한 container로 확장하고, 태그 배지를 카드 썸네일 내부의 왼쪽 상단/왼쪽 하단에 배치한다. 레거시 화면에 include하거나 레거시 카드 구현을 수정하지 않는다.
#### Figma References
- First tag: 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=20-3840&m=dev
- Free tag: 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=20-3843&m=dev
- Free + Original + First example: 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=20-3815&m=dev
- Point + Original + First example: 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=20-3814&m=dev
#### Tag Types
| Tag | 위치 | 구현 기준 | 리소스/텍스트 |
| --- | --- | --- | --- |
| `Original` | 왼쪽 상단 | Figma 예시의 24dp 정사각 original audio tag | `ic_content_tag_original` |
| `First` | 왼쪽 상단 | `#FF34B8` 배경 + 별 아이콘 + `FIRST` 텍스트 | `ic_content_tag_first_star` |
| `Point` | 왼쪽 하단 | Figma 예시의 24dp 정사각 point tag | `ic_content_tag_point` |
| `Free` | 왼쪽 하단 | `#052742` 배경 + 다국어 텍스트 | `@string/audio_content_tag_free` |
#### Layout Requirements
- 태그 높이는 Figma 기준 `24dp`를 사용한다.
- Original과 Point는 `24dp x 24dp` 아이콘 태그로 표시한다.
- First는 `62dp x 24dp` 배지로 표시한다.
- First 텍스트는 Figma `20:3840` 기준 `Phosphate Solid`, `16sp`, white, 단일 행, 대문자 `FIRST`로 표시한다.
- First 텍스트 위치는 배지 좌측 `20dp`, 상단 `2dp`를 기준으로 하며, 별 아이콘은 좌측 `2dp`, 상단 `4dp`, `17dp x 17dp`로 배치한다.
- Free는 높이 `24dp`, 최소 폭 `34dp`, 가로 `wrap_content` 배지로 표시한다. 한국어 `무료`는 Figma 기준 `34dp` 폭으로 보이고, 다른 locale 문구가 더 길면 텍스트가 잘리지 않도록 좌우 padding을 유지한 채 폭을 확장한다.
- 왼쪽 상단 tag row는 썸네일의 `start=0`, `top=0`에 배치한다.
- 왼쪽 하단 tag row는 썸네일의 `start=0`, `bottom=0`에 배치한다.
- 상단 row에서 Original과 First가 함께 있으면 항상 Original, First 순서로 표시한다.
- 하단 row에서 Point와 Free가 함께 있으면 Point, Free 순서로 표시한다.
- 태그 row는 썸네일 radius와 함께 잘려야 하며 카드 바깥으로 넘치지 않아야 한다.
#### Data Contract Requirements
- 태그 표시는 순수 Kotlin contract로 관리한다.
- `AudioContentTag` enum에 `Original`, `First`, `Point`, `Free`를 정의한다.
- `AudioContentCardView``setTags(tags: Set<AudioContentTag>)` API를 제공한다.
- 태그가 비어 있으면 모든 태그 row를 숨긴다.
- `setContent(title, creatorName)` 기존 API는 유지한다.
- `thumbnailView()` 기존 API는 유지해 호출부 이미지 로딩 방식을 변경하지 않는다.
#### Edge Cases
- 동일 태그가 중복 전달되면 한 번만 표시한다.
- 전달 순서와 무관하게 위치별 고정 순서를 적용한다.
- Free와 Point가 모두 전달되면 둘 다 왼쪽 하단에 표시한다.
- Original과 First가 모두 전달되면 둘 다 왼쪽 상단에 표시한다.
- 태그가 없는 기존 사용처는 현재와 같은 UI를 유지한다.
---
## 8. UX / UI Expectations
- 태그 배지는 썸네일 위에 직접 겹쳐 보이며, 제목/크리에이터 label 영역에는 영향을 주지 않는다.
- large, medium, small 카드 모두 동일한 태그 크기와 위치를 사용한다.
- Figma 예시처럼 Original/First는 상단 왼쪽에서 붙어 있고, Point/Free는 하단 왼쪽에서 붙어 있다.
- Free 태그는 string resource 기반으로 현재 locale에 맞는 문구를 표시한다.
- 장식 아이콘은 접근성 노출이 필요하지 않으면 `contentDescription=@null`로 둔다.
---
## 9. Technical Constraints
- 현재 프로젝트는 Android XML Views + Kotlin custom View 패턴을 사용한다.
- 신규 Kotlin 코드는 `kr.co.vividnext.sodalive.v2.widget` 패키지 하위에 작성한다.
- 기존 `AudioContentCardView`의 public API 호환성을 깨지 않는다.
- 기존 카드 크기 계산은 v2 신규 위젯의 `AudioContentCardSize`를 유지한다.
- 색상과 typography는 기존 token을 우선 사용하되, Figma 고유 색상(`#FF34B8`, `#052742`)은 전용 drawable 리소스로 최소 추가한다.
- 다국어 문자열은 `values/strings.xml`, `values-en/strings.xml`, `values-ja/strings.xml`에 추가한다.
- 아이콘 리소스는 `ic_content_tag_original`, `ic_content_tag_point`, `ic_content_tag_first_star` 이름으로 제공한다.
---
## 10. Metrics
- `AudioContentCardView`에서 4가지 태그 타입을 모두 표시할 수 있다.
- Original + First 조합은 왼쪽 상단에 Original, First 순서로 표시된다.
- Point + Free 조합은 왼쪽 하단에 Point, Free 순서로 표시된다.
- Free 태그는 string resource를 통해 다국어 처리된다.
- 태그가 없는 기존 오디오 콘텐츠 카드 UI는 변경되지 않는다.
- 관련 unit test와 Android resource merge/build가 성공한다.
---
## 11. Open Questions
- 사용자 확인으로 대상 위젯은 v2 패키지 아래 신규 위젯인 `AudioContentCardView``view_audio_content_card.xml`로 확정한다.
- 레거시 화면에는 포함하지 않고, 신규 위젯 자체의 표시 기능으로만 제공한다.
- 이번 작업은 문서 생성 범위이며, 구현은 계획/TASK 문서를 기준으로 별도 진행한다.