feat(content): 전체보기 UI 모델 매핑을 추가한다

This commit is contained in:
2026-06-27 08:29:19 +09:00
parent b772e5416e
commit 0a4b88fabe
5 changed files with 184 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
package kr.co.vividnext.sodalive.v2.main.content.overview.model
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewItemResponse
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewPageResponse
import kr.co.vividnext.sodalive.v2.widget.AudioContentTag
fun ContentOverviewPageResponse.toContent(): ContentOverviewUiState.Content {
return ContentOverviewUiState.Content(
type = type,
totalCount = items.size,
items = items.map { it.toUiModel() },
page = page,
size = size,
hasNext = hasNext
)
}
fun ContentOverviewItemResponse.toUiModel(): ContentOverviewUiModel = ContentOverviewUiModel(
contentId = contentId,
title = title,
coverImage = coverImage,
creatorNickname = creatorNickname,
tags = toAudioContentTags(),
showAdultBadge = isAdult
)
private fun ContentOverviewItemResponse.toAudioContentTags(): Set<AudioContentTag> = buildSet {
if (isOriginalSeries) add(AudioContentTag.Original)
if (isFirstContent) add(AudioContentTag.First)
if (isPointAvailable) add(AudioContentTag.Point)
if (price == 0) add(AudioContentTag.Free)
}

View File

@@ -0,0 +1,21 @@
package kr.co.vividnext.sodalive.v2.main.content.overview.model
import androidx.annotation.StringRes
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewType
import kr.co.vividnext.sodalive.v2.widget.AudioContentTag
data class ContentOverviewUiModel(
val contentId: Long,
val title: String,
val coverImage: String?,
val creatorNickname: String,
val tags: Set<AudioContentTag>,
val showAdultBadge: Boolean
)
@StringRes
fun ContentOverviewType.toTitleResId(): Int = when (this) {
ContentOverviewType.NEW_AND_HOT_AUDIO -> R.string.content_recommendation_section_new_and_hot
ContentOverviewType.FIRST_AUDIO_CONTENT -> R.string.home_recommendation_section_first_audio_contents
}

View File

@@ -0,0 +1,19 @@
package kr.co.vividnext.sodalive.v2.main.content.overview.model
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewType
sealed interface ContentOverviewUiState {
data class Loading(val type: ContentOverviewType) : ContentOverviewUiState
data class Empty(val type: ContentOverviewType, val totalCount: Int) : ContentOverviewUiState
data class Error(val type: ContentOverviewType, val message: String?) : ContentOverviewUiState
data class Content(
val type: ContentOverviewType,
val totalCount: Int,
val items: List<ContentOverviewUiModel>,
val page: Int,
val size: Int,
val hasNext: Boolean,
val isLoadingMore: Boolean = false,
val paginationErrorMessage: String? = null
) : ContentOverviewUiState
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

View File

@@ -0,0 +1,112 @@
package kr.co.vividnext.sodalive.v2.main.content.overview
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewItemResponse
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewPageResponse
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewType
import kr.co.vividnext.sodalive.v2.main.content.overview.model.toContent
import kr.co.vividnext.sodalive.v2.main.content.overview.model.toTitleResId
import kr.co.vividnext.sodalive.v2.main.content.overview.model.toUiModel
import kr.co.vividnext.sodalive.v2.widget.AudioContentTag
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class ContentOverviewMapperTest {
@Test
fun `type title resource maps to overview screen title`() {
assertEquals(
R.string.content_recommendation_section_new_and_hot,
ContentOverviewType.NEW_AND_HOT_AUDIO.toTitleResId()
)
assertEquals(
R.string.home_recommendation_section_first_audio_contents,
ContentOverviewType.FIRST_AUDIO_CONTENT.toTitleResId()
)
}
@Test
fun `audio item maps tags and adult badge`() {
val item = item(
price = 0,
isPointAvailable = true,
isFirstContent = true,
isOriginalSeries = true,
isAdult = true
).toUiModel()
assertEquals(
setOf(
AudioContentTag.Original,
AudioContentTag.First,
AudioContentTag.Point,
AudioContentTag.Free
),
item.tags
)
assertTrue(item.showAdultBadge)
}
@Test
fun `paid non adult item has no free tag and hides adult badge`() {
val item = item(price = 500, isAdult = false).toUiModel()
assertFalse(item.tags.contains(AudioContentTag.Free))
assertFalse(item.showAdultBadge)
}
@Test
fun `page response preserves paging metadata and type`() {
val content = pageResponse(
type = ContentOverviewType.FIRST_AUDIO_CONTENT,
page = 2,
size = 20,
hasNext = true,
items = listOf(item(contentId = 7L))
).toContent()
assertEquals(ContentOverviewType.FIRST_AUDIO_CONTENT, content.type)
assertEquals(2, content.page)
assertEquals(20, content.size)
assertTrue(content.hasNext)
assertEquals(listOf(7L), content.items.map { it.contentId })
}
private fun pageResponse(
type: ContentOverviewType = ContentOverviewType.NEW_AND_HOT_AUDIO,
items: List<ContentOverviewItemResponse> = listOf(item()),
page: Int = 0,
size: Int = 20,
hasNext: Boolean = false
) = ContentOverviewPageResponse(
type = type,
items = items,
page = page,
size = size,
hasNext = hasNext
)
private fun item(
contentId: Long = 1L,
title: String = "content",
coverImage: String? = "https://example.com/image.png",
price: Int = 100,
isAdult: Boolean = false,
isPointAvailable: Boolean = false,
isFirstContent: Boolean = false,
isOriginalSeries: Boolean = false,
creatorNickname: String = "creator"
) = ContentOverviewItemResponse(
contentId = contentId,
title = title,
coverImage = coverImage,
price = price,
isAdult = isAdult,
isPointAvailable = isPointAvailable,
isFirstContent = isFirstContent,
isOriginalSeries = isOriginalSeries,
creatorNickname = creatorNickname
)
}