feat(content): 전체보기 UI 모델 매핑을 추가한다
This commit is contained in:
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-mdpi/ic_feed_community_heart_fill.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_feed_community_heart_fill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 401 B |
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user