fix(content-ranking): 카드 UI 크기를 반응형 적용한다

This commit is contained in:
2026-07-10 10:16:36 +09:00
parent e77d0a1d5b
commit abd084cff8
4 changed files with 204 additions and 33 deletions

View File

@@ -6,6 +6,7 @@ import android.graphics.RenderEffect
import android.graphics.Shader import android.graphics.Shader
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
@@ -13,6 +14,7 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingResponsiveScale
import kotlin.math.roundToInt import kotlin.math.roundToInt
open class ContentRankingGridCardView @JvmOverloads constructor( open class ContentRankingGridCardView @JvmOverloads constructor(
@@ -29,6 +31,7 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
private var titleText: TextView? = null private var titleText: TextView? = null
private var creatorText: TextView? = null private var creatorText: TextView? = null
private var currentItem: ContentRankingItem? = null private var currentItem: ContentRankingItem? = null
private var currentTextStyle: ContentRankingTextStyle? = null
private var clickListener: ((ContentRankingItem) -> Unit)? = null private var clickListener: ((ContentRankingItem) -> Unit)? = null
override fun onFinishInflate() { override fun onFinishInflate() {
@@ -48,11 +51,13 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
fun bind(item: ContentRankingItem) { fun bind(item: ContentRankingItem) {
currentItem = item currentItem = item
val textStyle = currentTextStyle ?: defaultTextStyle()
applyTextStyle(textStyle)
requireNotNull(rankText).apply { requireNotNull(rankText).apply {
text = item.rank.toString() text = item.rank.toString()
applyContentRankingRankGradient() applyContentRankingRankGradient()
} }
bindDelta(item) bindDelta(item, textStyle)
requireNotNull(titleText).apply { requireNotNull(titleText).apply {
text = item.displayContentName(context.getString(R.string.content_ranking_inaccessible_info)) text = item.displayContentName(context.getString(R.string.content_ranking_inaccessible_info))
visibility = if (text.isNullOrBlank()) View.INVISIBLE else View.VISIBLE visibility = if (text.isNullOrBlank()) View.INVISIBLE else View.VISIBLE
@@ -79,34 +84,58 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
currentItem?.let(::applyAccessState) currentItem?.let(::applyAccessState)
} }
private fun bindDelta(item: ContentRankingItem) { private fun bindDelta(
item: ContentRankingItem,
textStyle: ContentRankingTextStyle
) {
requireNotNull(deltaGroup).visibility = if (item.showRankChange) View.VISIBLE else View.GONE requireNotNull(deltaGroup).visibility = if (item.showRankChange) View.VISIBLE else View.GONE
if (!item.showRankChange) return if (!item.showRankChange) return
val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount) val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount)
applyDeltaContainer(presentation) val scale = RankingResponsiveScale.fromResources(resources)
applyDeltaContainer(presentation, scale)
requireNotNull(deltaIcon).apply { requireNotNull(deltaIcon).apply {
setImageResource(presentation.iconRes) setImageResource(presentation.iconRes)
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply { layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
width = presentation.iconWidthDp.dpToPx() width = presentation.iconWidthDp.dpToPx(scale)
height = presentation.iconHeightDp.dpToPx() height = presentation.iconHeightDp.dpToPx(scale)
marginStart = presentation.iconMarginStartDp.dpToPx() marginStart = presentation.iconMarginStartDp.dpToPx(scale)
} }
} }
requireNotNull(deltaAmountText).apply { requireNotNull(deltaAmountText).apply {
text = presentation.amountText text = presentation.amountText
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.deltaTextSizeSp)
visibility = if (presentation.showAmount) View.VISIBLE else View.GONE visibility = if (presentation.showAmount) View.VISIBLE else View.GONE
} }
} }
private fun applyDeltaContainer(presentation: ContentRankingDeltaPresentation) { private fun applyDeltaContainer(
presentation: ContentRankingDeltaPresentation,
scale: Float
) {
requireNotNull(deltaGroup).apply { requireNotNull(deltaGroup).apply {
setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0) setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0)
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx() else 0 val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx(scale) else 0
setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom) setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom)
} }
} }
private fun applyTextStyle(style: ContentRankingTextStyle) {
requireNotNull(rankText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.rankTextSizeSp)
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.creatorTextSizeSp)
requireNotNull(deltaAmountText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.deltaTextSizeSp)
}
private fun defaultTextStyle(): ContentRankingTextStyle {
val scale = RankingResponsiveScale.fromResources(resources)
return if (this is ContentRankingSmallGridCardView) {
ContentRankingTextStyle.smallGrid(scale)
} else {
ContentRankingTextStyle.mediumGrid(scale)
}
}
private fun applyAccessState(item: ContentRankingItem) { private fun applyAccessState(item: ContentRankingItem) {
applyBlur(item.isInaccessible) applyBlur(item.isInaccessible)
isClickable = item.isTouchable && clickListener != null isClickable = item.isTouchable && clickListener != null
@@ -120,26 +149,32 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
private fun positionMedium(size: ContentRankingCardSize) { private fun positionMedium(size: ContentRankingCardSize) {
val scale = size.widthPx / 185f val scale = size.widthPx / 185f
val textStyle = ContentRankingTextStyle.mediumGrid(RankingResponsiveScale.fromResources(resources))
currentTextStyle = textStyle
requireNotNull(rankText).apply { requireNotNull(rankText).apply {
textSize = 54f setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.rankTextSizeSp)
layoutParams = LayoutParams((55 * scale).roundToInt(), (75 * scale).roundToInt()) layoutParams = LayoutParams((55 * scale).roundToInt(), (75 * scale).roundToInt())
setPadding(0, 0, 0, (6 * scale).roundToInt()) setPadding(0, 0, 0, (6 * scale).roundToInt())
} }
requireNotNull(titleText).textSize = 22f requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.creatorTextSizeSp)
placeDelta(left = 10, top = 70, scale = scale) placeDelta(left = 10, top = 70, scale = scale)
placeLabel(width = 165, top = 136, scale = scale, size = size) placeLabel(width = 165, top = 136, scale = scale, size = size)
} }
private fun positionSmall(size: ContentRankingCardSize) { private fun positionSmall(size: ContentRankingCardSize) {
val scale = size.widthPx / 122f val scale = size.widthPx / 122f
val textStyle = ContentRankingTextStyle.smallGrid(RankingResponsiveScale.fromResources(resources))
currentTextStyle = textStyle
requireNotNull(rankText).apply { requireNotNull(rankText).apply {
textSize = 40f setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.rankTextSizeSp)
layoutParams = LayoutParams((42 * scale).roundToInt(), (56 * scale).roundToInt()).apply { layoutParams = LayoutParams((42 * scale).roundToInt(), (56 * scale).roundToInt()).apply {
leftMargin = (8 * scale).roundToInt() leftMargin = (8 * scale).roundToInt()
} }
setPadding(0, 0, 0, (5 * scale).roundToInt()) setPadding(0, 0, 0, (5 * scale).roundToInt())
} }
requireNotNull(titleText).textSize = 14f requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.creatorTextSizeSp)
placeDelta(left = 10, top = 49, scale = scale) placeDelta(left = 10, top = 49, scale = scale)
placeLabel(width = 102, top = 81, scale = scale, size = size) placeLabel(width = 102, top = 81, scale = scale, size = size)
} }
@@ -178,7 +213,7 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
} }
} }
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt() private fun Int.dpToPx(scale: Float = 1f): Int = (this * scale * resources.displayMetrics.density).roundToInt()
private companion object { private companion object {
const val SMALL_THRESHOLD_PX = 140 const val SMALL_THRESHOLD_PX = 140

View File

@@ -6,6 +6,7 @@ import android.graphics.RenderEffect
import android.graphics.Shader import android.graphics.Shader
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
@@ -13,6 +14,7 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingResponsiveScale
import kotlin.math.roundToInt import kotlin.math.roundToInt
class ContentRankingHorizontalCardView @JvmOverloads constructor( class ContentRankingHorizontalCardView @JvmOverloads constructor(
@@ -48,11 +50,13 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
fun bind(item: ContentRankingItem) { fun bind(item: ContentRankingItem) {
currentItem = item currentItem = item
val textStyle = ContentRankingTextStyle.horizontal(RankingResponsiveScale.fromResources(resources))
applyTextStyle(textStyle)
requireNotNull(rankText).apply { requireNotNull(rankText).apply {
text = item.rank.toString() text = item.rank.toString()
applyContentRankingRankGradient() applyContentRankingRankGradient()
} }
bindDelta(item) bindDelta(item, textStyle)
val inaccessibleMessage = context.getString(R.string.content_ranking_inaccessible_info) val inaccessibleMessage = context.getString(R.string.content_ranking_inaccessible_info)
requireNotNull(titleText).text = item.displayContentName(inaccessibleMessage) requireNotNull(titleText).text = item.displayContentName(inaccessibleMessage)
requireNotNull(creatorText).text = item.displayCreatorName() requireNotNull(creatorText).text = item.displayCreatorName()
@@ -69,6 +73,7 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
height = size.heightPx height = size.heightPx
} }
positionViews(size) positionViews(size)
applyTextStyle(ContentRankingTextStyle.horizontal(RankingResponsiveScale.fromResources(resources)))
} }
fun imageView(): ImageView = requireNotNull(image) fun imageView(): ImageView = requireNotNull(image)
@@ -78,35 +83,51 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
currentItem?.let(::applyAccessState) currentItem?.let(::applyAccessState)
} }
private fun bindDelta(item: ContentRankingItem) { private fun bindDelta(
item: ContentRankingItem,
textStyle: ContentRankingTextStyle
) {
val deltaGroup = findViewById<View>(R.id.ll_content_ranking_delta) val deltaGroup = findViewById<View>(R.id.ll_content_ranking_delta)
deltaGroup.visibility = if (item.showRankChange) View.VISIBLE else View.GONE deltaGroup.visibility = if (item.showRankChange) View.VISIBLE else View.GONE
if (!item.showRankChange) return if (!item.showRankChange) return
val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount) val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount)
applyDeltaContainer(presentation) val scale = RankingResponsiveScale.fromResources(resources)
applyDeltaContainer(presentation, scale)
requireNotNull(deltaIcon).apply { requireNotNull(deltaIcon).apply {
setImageResource(presentation.iconRes) setImageResource(presentation.iconRes)
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply { layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
width = presentation.iconWidthDp.dpToPx() width = presentation.iconWidthDp.dpToPx(scale)
height = presentation.iconHeightDp.dpToPx() height = presentation.iconHeightDp.dpToPx(scale)
marginStart = presentation.iconMarginStartDp.dpToPx() marginStart = presentation.iconMarginStartDp.dpToPx(scale)
} }
} }
requireNotNull(deltaAmountText).apply { requireNotNull(deltaAmountText).apply {
text = presentation.amountText text = presentation.amountText
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.deltaTextSizeSp)
visibility = if (presentation.showAmount) View.VISIBLE else View.GONE visibility = if (presentation.showAmount) View.VISIBLE else View.GONE
} }
} }
private fun applyDeltaContainer(presentation: ContentRankingDeltaPresentation) { private fun applyDeltaContainer(
presentation: ContentRankingDeltaPresentation,
scale: Float
) {
findViewById<View>(R.id.ll_content_ranking_delta).apply { findViewById<View>(R.id.ll_content_ranking_delta).apply {
setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0) setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0)
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx() else 0 val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx(scale) else 0
setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom) setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom)
} }
} }
private fun applyTextStyle(style: ContentRankingTextStyle) {
requireNotNull(rankText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.rankTextSizeSp)
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.creatorTextSizeSp)
requireNotNull(inaccessibleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(deltaAmountText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.deltaTextSizeSp)
}
private fun applyAccessState(item: ContentRankingItem) { private fun applyAccessState(item: ContentRankingItem) {
applyBlur(item.isInaccessible) applyBlur(item.isInaccessible)
isClickable = item.isTouchable && clickListener != null isClickable = item.isTouchable && clickListener != null
@@ -157,5 +178,5 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
} }
} }
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt() private fun Int.dpToPx(scale: Float = 1f): Int = (this * scale * resources.displayMetrics.density).roundToInt()
} }

View File

@@ -6,6 +6,7 @@ import android.graphics.RenderEffect
import android.graphics.Shader import android.graphics.Shader
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewOutlineProvider import android.view.ViewOutlineProvider
@@ -13,6 +14,7 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingResponsiveScale
import kotlin.math.roundToInt import kotlin.math.roundToInt
class ContentRankingLargeCardView @JvmOverloads constructor( class ContentRankingLargeCardView @JvmOverloads constructor(
@@ -50,11 +52,13 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
fun bind(item: ContentRankingItem) { fun bind(item: ContentRankingItem) {
currentItem = item currentItem = item
val textStyle = ContentRankingTextStyle.large(RankingResponsiveScale.fromResources(resources))
applyTextStyle(textStyle)
requireNotNull(rankText).apply { requireNotNull(rankText).apply {
text = item.rank.toString() text = item.rank.toString()
applyContentRankingRankGradient() applyContentRankingRankGradient()
} }
bindDelta(item) bindDelta(item, textStyle)
requireNotNull(titleText).apply { requireNotNull(titleText).apply {
text = item.displayContentName(context.getString(R.string.content_ranking_inaccessible_info)) text = item.displayContentName(context.getString(R.string.content_ranking_inaccessible_info))
visibility = if (text.isNullOrBlank()) View.INVISIBLE else View.VISIBLE visibility = if (text.isNullOrBlank()) View.INVISIBLE else View.VISIBLE
@@ -72,6 +76,7 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
height = size.heightPx height = size.heightPx
} }
positionViews(size) positionViews(size)
applyTextStyle(ContentRankingTextStyle.large(RankingResponsiveScale.fromResources(resources)))
} }
fun imageView(): ImageView = contentImageView() fun imageView(): ImageView = contentImageView()
@@ -83,34 +88,49 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
currentItem?.let(::applyAccessState) currentItem?.let(::applyAccessState)
} }
private fun bindDelta(item: ContentRankingItem) { private fun bindDelta(
item: ContentRankingItem,
textStyle: ContentRankingTextStyle
) {
requireNotNull(deltaGroup).visibility = if (item.showRankChange) View.VISIBLE else View.GONE requireNotNull(deltaGroup).visibility = if (item.showRankChange) View.VISIBLE else View.GONE
if (!item.showRankChange) return if (!item.showRankChange) return
val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount) val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount)
applyDeltaContainer(presentation) val scale = RankingResponsiveScale.fromResources(resources)
applyDeltaContainer(presentation, scale)
requireNotNull(deltaIcon).apply { requireNotNull(deltaIcon).apply {
setImageResource(presentation.iconRes) setImageResource(presentation.iconRes)
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply { layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
width = presentation.iconWidthDp.dpToPx() width = presentation.iconWidthDp.dpToPx(scale)
height = presentation.iconHeightDp.dpToPx() height = presentation.iconHeightDp.dpToPx(scale)
marginStart = presentation.iconMarginStartDp.dpToPx() marginStart = presentation.iconMarginStartDp.dpToPx(scale)
} }
} }
requireNotNull(deltaAmountText).apply { requireNotNull(deltaAmountText).apply {
text = presentation.amountText text = presentation.amountText
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.deltaTextSizeSp)
visibility = if (presentation.showAmount) View.VISIBLE else View.GONE visibility = if (presentation.showAmount) View.VISIBLE else View.GONE
} }
} }
private fun applyDeltaContainer(presentation: ContentRankingDeltaPresentation) { private fun applyDeltaContainer(
presentation: ContentRankingDeltaPresentation,
scale: Float
) {
requireNotNull(deltaGroup).apply { requireNotNull(deltaGroup).apply {
setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0) setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0)
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx() else 0 val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx(scale) else 0
setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom) setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom)
} }
} }
private fun applyTextStyle(style: ContentRankingTextStyle) {
requireNotNull(rankText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.rankTextSizeSp)
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.creatorTextSizeSp)
requireNotNull(deltaAmountText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.deltaTextSizeSp)
}
private fun applyAccessState(item: ContentRankingItem) { private fun applyAccessState(item: ContentRankingItem) {
applyBlur(item.isInaccessible) applyBlur(item.isInaccessible)
isClickable = item.isTouchable && clickListener != null isClickable = item.isTouchable && clickListener != null
@@ -159,7 +179,7 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
} }
} }
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt() private fun Int.dpToPx(scale: Float = 1f): Int = (this * scale * resources.displayMetrics.density).roundToInt()
private companion object { private companion object {
const val FIGMA_WIDTH = 374 const val FIGMA_WIDTH = 374

View File

@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.widget.contentranking
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -144,6 +145,83 @@ class ContentRankingCardViewTest {
assertEquals(View.VISIBLE, view.findViewById<View>(R.id.iv_content_ranking_background).visibility) assertEquals(View.VISIBLE, view.findViewById<View>(R.id.iv_content_ranking_background).visibility)
} }
@Test
fun `large card는 402dp 미만에서 실제 text size를 축소한다`() {
val view = inflateView<ContentRankingLargeCardView>(
layoutResId = R.layout.view_content_ranking_large_card,
screenWidthDp = 360
)
view.bind(sampleItem(rank = 1))
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_rank), 96f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_title), 22f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_creator), 12f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_delta_amount), 16f * 360f / 402f)
}
@Test
fun `medium grid card는 402dp 미만에서 실제 text size를 축소한다`() {
val view = inflateView<ContentRankingMediumGridCardView>(
layoutResId = R.layout.view_content_ranking_medium_grid_card,
screenWidthDp = 360
)
view.setCardSize(ContentRankingCardSize(widthPx = 185, heightPx = 185))
view.bind(sampleItem(rank = 2))
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_rank), 54f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_title), 22f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_creator), 12f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_delta_amount), 16f * 360f / 402f)
}
@Test
fun `small grid card는 402dp 미만에서 조건부 baseline text size를 축소한다`() {
val view = inflateView<ContentRankingSmallGridCardView>(
layoutResId = R.layout.view_content_ranking_small_grid_card,
screenWidthDp = 360
)
view.setCardSize(ContentRankingCardSize(widthPx = 122, heightPx = 122))
view.bind(sampleItem(rank = 8))
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_rank), 36f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_title), 14f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_creator), 12f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_delta_amount), 16f * 360f / 402f)
}
@Test
fun `small grid card는 card size 적용 전 bind에서도 402dp 미만 text size를 축소한다`() {
val view = inflateView<ContentRankingSmallGridCardView>(
layoutResId = R.layout.view_content_ranking_small_grid_card,
screenWidthDp = 360
)
view.bind(sampleItem(rank = 8))
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_rank), 36f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_title), 14f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_creator), 12f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_delta_amount), 16f * 360f / 402f)
}
@Test
fun `horizontal card는 402dp 미만에서 실제 text size를 축소한다`() {
val view = inflateView<ContentRankingHorizontalCardView>(
layoutResId = R.layout.view_content_ranking_horizontal_card,
screenWidthDp = 360
)
view.bind(sampleItem(rank = 11))
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_rank), 40f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_title), 18f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_creator), 14f * 360f / 402f)
assertTextSizeSp(view.findViewById(R.id.tv_content_ranking_delta_amount), 16f * 360f / 402f)
}
@Test @Test
fun `large view holder는 배경 blur와 전경 non blur coverImage를 모두 로드한다`() { fun `large view holder는 배경 blur와 전경 non blur coverImage를 모두 로드한다`() {
val source = projectFile( val source = projectFile(
@@ -161,11 +239,28 @@ class ContentRankingCardViewTest {
) )
} }
private inline fun <reified T : View> inflateView(layoutResId: Int): T { private inline fun <reified T : View> inflateView(
val context = ApplicationProvider.getApplicationContext<Context>() layoutResId: Int,
screenWidthDp: Int? = null
): T {
val baseContext = ApplicationProvider.getApplicationContext<Context>()
val context = if (screenWidthDp == null) {
baseContext
} else {
baseContext.createConfigurationContext(
Configuration(baseContext.resources.configuration).apply { this.screenWidthDp = screenWidthDp }
)
}
return LayoutInflater.from(context).inflate(layoutResId, null, false) as T return LayoutInflater.from(context).inflate(layoutResId, null, false) as T
} }
private fun assertTextSizeSp(
view: TextView,
expectedSp: Float
) {
assertEquals(expectedSp, view.textSize / view.resources.displayMetrics.scaledDensity, 0.0001f)
}
private fun assertRankTextBox( private fun assertRankTextBox(
view: TextView, view: TextView,
expectedWidth: Int, expectedWidth: Int,