feat(widget): 콘텐츠 랭킹 위젯을 추가한다
This commit is contained in:
@@ -21,8 +21,8 @@
|
||||
## 파일 구조
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItem.kt`
|
||||
- 랭킹 UI에 필요한 순수 데이터 모델과 차단 관계 상태 계산을 정의한다.
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingChangeType.kt`
|
||||
- `Increase`, `Decrease`, `Stay`, `New` 순위 변동 타입을 정의한다.
|
||||
- Rename/Move: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingChangeType.kt` -> `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/ranking/RankingChangeType.kt`
|
||||
- `Increase`, `Decrease`, `Stay`, `New` 순위 변동 타입을 크리에이터/콘텐츠 랭킹 공용 타입으로 정의한다.
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingCardVariant.kt`
|
||||
- `Large`, `Compact`, `Horizontal` 카드 UI variant를 정의한다.
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingPlacement.kt`
|
||||
@@ -30,7 +30,7 @@
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingLayoutCalculator.kt`
|
||||
- 부모 폭, horizontal gap, row count 기준으로 item width/height를 계산한다.
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingDeltaPresentation.kt`
|
||||
- 순위 변동 타입별 아이콘과 숫자 표시 여부를 정의한다. `Stay`와 `New`는 숫자를 표시하지 않는다.
|
||||
- 공용 `RankingChangeType`별 아이콘과 숫자 표시 여부를 정의한다. `Stay`와 `New`는 숫자를 표시하지 않는다.
|
||||
- Create: `app/src/main/res/layout/view_creator_ranking_large_card.xml`
|
||||
- 1위 전용 큰 정사각형 카드 layout을 정의한다.
|
||||
- Create: `app/src/main/res/layout/view_creator_ranking_compact_card.xml`
|
||||
@@ -215,7 +215,7 @@ Expected: `BUILD SUCCESSFUL`
|
||||
|
||||
**Files:**
|
||||
- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItemTest.kt`
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingChangeType.kt`
|
||||
- Rename/Move: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingChangeType.kt` -> `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/ranking/RankingChangeType.kt`
|
||||
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/creatorranking/CreatorRankingItem.kt`
|
||||
|
||||
- [x] **Step 1: RED - 접근 가능/불가 표시 정책 테스트 추가**
|
||||
@@ -223,6 +223,7 @@ Expected: `BUILD SUCCESSFUL`
|
||||
```kotlin
|
||||
package kr.co.vividnext.sodalive.v2.widget.creatorranking
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -271,7 +272,7 @@ class CreatorRankingItemTest {
|
||||
creatorId: Long = 1L,
|
||||
rank: Int = 1,
|
||||
previousRank: Int? = 5,
|
||||
rankChangeType: CreatorRankingChangeType = CreatorRankingChangeType.Increase,
|
||||
rankChangeType: RankingChangeType = RankingChangeType.Increase,
|
||||
rankChangeAmount: Int = 4,
|
||||
creatorName: String = "크리에이터 이름",
|
||||
imageUrl: String = "https://example.com/image.png",
|
||||
@@ -298,9 +299,9 @@ Expected: `Unresolved reference 'CreatorRankingItem'`로 실패한다.
|
||||
- [x] **Step 3: GREEN - 순수 상태 모델 추가**
|
||||
|
||||
```kotlin
|
||||
package kr.co.vividnext.sodalive.v2.widget.creatorranking
|
||||
package kr.co.vividnext.sodalive.v2.widget.ranking
|
||||
|
||||
enum class CreatorRankingChangeType {
|
||||
enum class RankingChangeType {
|
||||
Increase,
|
||||
Decrease,
|
||||
Stay,
|
||||
@@ -311,11 +312,13 @@ enum class CreatorRankingChangeType {
|
||||
```kotlin
|
||||
package kr.co.vividnext.sodalive.v2.widget.creatorranking
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
|
||||
|
||||
data class CreatorRankingItem(
|
||||
val creatorId: Long,
|
||||
val rank: Int,
|
||||
val previousRank: Int?,
|
||||
val rankChangeType: CreatorRankingChangeType,
|
||||
val rankChangeType: RankingChangeType,
|
||||
val rankChangeAmount: Int,
|
||||
val creatorName: String,
|
||||
val imageUrl: String,
|
||||
@@ -354,6 +357,7 @@ Expected: `BUILD SUCCESSFUL`
|
||||
package kr.co.vividnext.sodalive.v2.widget.creatorranking
|
||||
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
@@ -364,7 +368,7 @@ class CreatorRankingDeltaPresentationTest {
|
||||
|
||||
@Test
|
||||
fun `increase shows caret and amount`() {
|
||||
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Increase, amount = 4)
|
||||
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.Increase, amount = 4)
|
||||
|
||||
assertEquals(R.drawable.ic_rank_caret_increase, presentation.iconRes)
|
||||
assertTrue(presentation.showAmount)
|
||||
@@ -373,7 +377,7 @@ class CreatorRankingDeltaPresentationTest {
|
||||
|
||||
@Test
|
||||
fun `decrease shows caret and amount`() {
|
||||
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Decrease, amount = 4)
|
||||
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.Decrease, amount = 4)
|
||||
|
||||
assertEquals(R.drawable.ic_rank_caret_decrease, presentation.iconRes)
|
||||
assertTrue(presentation.showAmount)
|
||||
@@ -382,7 +386,7 @@ class CreatorRankingDeltaPresentationTest {
|
||||
|
||||
@Test
|
||||
fun `stay shows stay icon without amount`() {
|
||||
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.Stay, amount = 0)
|
||||
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.Stay, amount = 0)
|
||||
|
||||
assertEquals(R.drawable.ic_rank_caret_stay, presentation.iconRes)
|
||||
assertFalse(presentation.showAmount)
|
||||
@@ -391,7 +395,7 @@ class CreatorRankingDeltaPresentationTest {
|
||||
|
||||
@Test
|
||||
fun `new shows new image without amount`() {
|
||||
val presentation = CreatorRankingDeltaPresentation.from(CreatorRankingChangeType.New, amount = 0)
|
||||
val presentation = CreatorRankingDeltaPresentation.from(RankingChangeType.New, amount = 0)
|
||||
|
||||
assertEquals(R.drawable.ic_rank_new, presentation.iconRes)
|
||||
assertFalse(presentation.showAmount)
|
||||
@@ -413,6 +417,7 @@ package kr.co.vividnext.sodalive.v2.widget.creatorranking
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingChangeType
|
||||
|
||||
data class CreatorRankingDeltaPresentation(
|
||||
@DrawableRes val iconRes: Int,
|
||||
@@ -420,23 +425,23 @@ data class CreatorRankingDeltaPresentation(
|
||||
val amountText: String?
|
||||
) {
|
||||
companion object {
|
||||
fun from(type: CreatorRankingChangeType, amount: Int): CreatorRankingDeltaPresentation = when (type) {
|
||||
CreatorRankingChangeType.Increase -> CreatorRankingDeltaPresentation(
|
||||
fun from(type: RankingChangeType, amount: Int): CreatorRankingDeltaPresentation = when (type) {
|
||||
RankingChangeType.Increase -> CreatorRankingDeltaPresentation(
|
||||
iconRes = R.drawable.ic_rank_caret_increase,
|
||||
showAmount = true,
|
||||
amountText = amount.toString()
|
||||
)
|
||||
CreatorRankingChangeType.Decrease -> CreatorRankingDeltaPresentation(
|
||||
RankingChangeType.Decrease -> CreatorRankingDeltaPresentation(
|
||||
iconRes = R.drawable.ic_rank_caret_decrease,
|
||||
showAmount = true,
|
||||
amountText = amount.toString()
|
||||
)
|
||||
CreatorRankingChangeType.Stay -> CreatorRankingDeltaPresentation(
|
||||
RankingChangeType.Stay -> CreatorRankingDeltaPresentation(
|
||||
iconRes = R.drawable.ic_rank_caret_stay,
|
||||
showAmount = false,
|
||||
amountText = null
|
||||
)
|
||||
CreatorRankingChangeType.New -> CreatorRankingDeltaPresentation(
|
||||
RankingChangeType.New -> CreatorRankingDeltaPresentation(
|
||||
iconRes = R.drawable.ic_rank_new,
|
||||
showAmount = false,
|
||||
amountText = null
|
||||
@@ -619,8 +624,8 @@ Required common API:
|
||||
|
||||
Required behavior:
|
||||
- `CreatorRankingDeltaPresentation`을 사용해 rank delta icon과 amount 표시 여부를 결정한다.
|
||||
- `CreatorRankingChangeType.Stay`이면 숫자 없이 `ic_rank_caret_stay`만 표시한다.
|
||||
- `CreatorRankingChangeType.New`이면 `ic_rank_new`를 표시하고 rank delta 숫자는 숨긴다.
|
||||
- `RankingChangeType.Stay`이면 숫자 없이 `ic_rank_caret_stay`만 표시한다.
|
||||
- `RankingChangeType.New`이면 `ic_rank_new`를 표시하고 rank delta 숫자는 숨긴다.
|
||||
- `Increase`, `Decrease`는 change type별 caret icon과 `rankChangeAmount`를 표시한다.
|
||||
- `Large`와 `Compact`에서 `item.displayName(...)` 결과가 빈 문자열이면 name TextView를 숨기거나 빈 값으로 둔다.
|
||||
- `Large`와 `Compact`에서 name TextView를 숨겨도 dim gradient view는 숨기지 않는다.
|
||||
@@ -794,7 +799,7 @@ Expected: 신규 layout의 ViewBinding 생성 파일이 출력된다.
|
||||
- 결과:
|
||||
- `CreatorRankingAdapter.GRID_SPAN_COUNT`, `createSpanSizeLookup()`, `createGridLayoutManager(context)`를 추가해 1위/11위 이후 full span, 2위~7위 2열, 8위~10위 3열 구성을 호출부가 적용할 수 있게 했다.
|
||||
- API 31 미만 차단 이미지에는 기존 `BlurTransformation` 기반 Coil blur fallback을 적용하고, API 31 이상 `RenderEffect` 경로는 유지했다.
|
||||
- `CreatorRankingChangeType.New`는 pill 배경 없이 `36dp x 23dp` 아이콘으로 표시하고, caret 계열은 기존 `14dp x 14dp` pill 표시를 유지했다.
|
||||
- `RankingChangeType.New`는 pill 배경 없이 `36dp x 23dp` 아이콘으로 표시하고, caret 계열은 기존 `14dp x 14dp` pill 표시를 유지했다.
|
||||
- creator ranking 단위 테스트와 `:app:assembleDebug`는 모두 `BUILD SUCCESSFUL`로 통과했다.
|
||||
- Kotlin LSP는 현재 환경에 `.kt` 확장자 서버가 없어 `No LSP server configured for extension: .kt`로 진단을 수행할 수 없었다.
|
||||
- 보강 후 재리뷰에서 기존 Important 3건은 모두 해소됐고 새 Critical/Important 이슈는 없음을 확인했다.
|
||||
|
||||
Reference in New Issue
Block a user