feat(widget): 오디오 콘텐츠 카드 컴포넌트를 추가한다
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget
|
||||
|
||||
import androidx.annotation.StyleRes
|
||||
import kr.co.vividnext.sodalive.R
|
||||
|
||||
sealed class AudioContentCardSize(
|
||||
val cardWidthDp: Int,
|
||||
val thumbnailSizeDp: Int,
|
||||
val labelWidthDp: Int,
|
||||
val thumbnailLabelGapDp: Int,
|
||||
@get:StyleRes val titleStyleRes: Int,
|
||||
@get:StyleRes val creatorStyleRes: Int
|
||||
) {
|
||||
data object Large : AudioContentCardSize(
|
||||
cardWidthDp = 185,
|
||||
thumbnailSizeDp = 185,
|
||||
labelWidthDp = 185,
|
||||
thumbnailLabelGapDp = 11,
|
||||
titleStyleRes = R.style.Typography_Heading4,
|
||||
creatorStyleRes = R.style.Typography_Body5
|
||||
)
|
||||
|
||||
data object Medium : AudioContentCardSize(
|
||||
cardWidthDp = 163,
|
||||
thumbnailSizeDp = 163,
|
||||
labelWidthDp = 151,
|
||||
thumbnailLabelGapDp = 8,
|
||||
titleStyleRes = R.style.Typography_Heading4,
|
||||
creatorStyleRes = R.style.Typography_Body5
|
||||
)
|
||||
|
||||
data object Small : AudioContentCardSize(
|
||||
cardWidthDp = 122,
|
||||
thumbnailSizeDp = 122,
|
||||
labelWidthDp = 114,
|
||||
thumbnailLabelGapDp = 8,
|
||||
titleStyleRes = R.style.Typography_Body1,
|
||||
creatorStyleRes = R.style.Typography_Caption2
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class AudioContentCardView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private var thumbnail: ImageView? = null
|
||||
private var labelContainer: LinearLayout? = null
|
||||
private var titleText: TextView? = null
|
||||
private var creatorText: TextView? = null
|
||||
|
||||
init {
|
||||
orientation = VERTICAL
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
thumbnail = findViewById(R.id.iv_audio_content_thumbnail)
|
||||
labelContainer = findViewById(R.id.ll_audio_content_label)
|
||||
titleText = findViewById(R.id.tv_audio_content_title)
|
||||
creatorText = findViewById(R.id.tv_audio_content_creator)
|
||||
setSize(AudioContentCardSize.Medium)
|
||||
}
|
||||
|
||||
fun setSize(size: AudioContentCardSize) {
|
||||
updateRootWidth(size.cardWidthDp.dpToPx())
|
||||
|
||||
requireNotNull(thumbnail).layoutParams = LayoutParams(
|
||||
size.thumbnailSizeDp.dpToPx(),
|
||||
size.thumbnailSizeDp.dpToPx()
|
||||
)
|
||||
|
||||
requireNotNull(labelContainer).layoutParams = LayoutParams(
|
||||
size.labelWidthDp.dpToPx(),
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
topMargin = size.thumbnailLabelGapDp.dpToPx()
|
||||
}
|
||||
|
||||
requireNotNull(titleText).setTextAppearance(size.titleStyleRes)
|
||||
requireNotNull(creatorText).apply {
|
||||
setTextAppearance(size.creatorStyleRes)
|
||||
layoutParams = LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
topMargin = TITLE_CREATOR_GAP_DP.dpToPx()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setContent(
|
||||
title: String,
|
||||
creatorName: String
|
||||
) {
|
||||
requireNotNull(titleText).text = title
|
||||
requireNotNull(creatorText).text = creatorName
|
||||
}
|
||||
|
||||
fun thumbnailView(): ImageView = requireNotNull(thumbnail)
|
||||
|
||||
private fun updateRootWidth(width: Int) {
|
||||
val currentLayoutParams = layoutParams
|
||||
layoutParams = if (currentLayoutParams == null) {
|
||||
ViewGroup.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
} else {
|
||||
currentLayoutParams.apply {
|
||||
this.width = width
|
||||
this.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
|
||||
|
||||
private companion object {
|
||||
const val TITLE_CREATOR_GAP_DP = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/gray_900" />
|
||||
<corners android:radius="@dimen/radius_14" />
|
||||
</shape>
|
||||
41
app/src/main/res/layout/view_audio_content_card.xml
Normal file
41
app/src/main/res/layout/view_audio_content_card.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<kr.co.vividnext.sodalive.v2.widget.AudioContentCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_audio_content_thumbnail"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_audio_content_card_thumbnail"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_audio_content_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_audio_content_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/white"
|
||||
tools:text="콘텐츠 제목" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_audio_content_creator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/gray_500"
|
||||
tools:text="크리에이터 이름" />
|
||||
</LinearLayout>
|
||||
</kr.co.vividnext.sodalive.v2.widget.AudioContentCardView>
|
||||
@@ -0,0 +1,38 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget
|
||||
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class AudioContentCardSizeTest {
|
||||
|
||||
@Test
|
||||
fun `large size matches figma contract`() {
|
||||
assertEquals(185, AudioContentCardSize.Large.cardWidthDp)
|
||||
assertEquals(185, AudioContentCardSize.Large.thumbnailSizeDp)
|
||||
assertEquals(185, AudioContentCardSize.Large.labelWidthDp)
|
||||
assertEquals(11, AudioContentCardSize.Large.thumbnailLabelGapDp)
|
||||
assertEquals(R.style.Typography_Heading4, AudioContentCardSize.Large.titleStyleRes)
|
||||
assertEquals(R.style.Typography_Body5, AudioContentCardSize.Large.creatorStyleRes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `medium size matches figma contract`() {
|
||||
assertEquals(163, AudioContentCardSize.Medium.cardWidthDp)
|
||||
assertEquals(163, AudioContentCardSize.Medium.thumbnailSizeDp)
|
||||
assertEquals(151, AudioContentCardSize.Medium.labelWidthDp)
|
||||
assertEquals(8, AudioContentCardSize.Medium.thumbnailLabelGapDp)
|
||||
assertEquals(R.style.Typography_Heading4, AudioContentCardSize.Medium.titleStyleRes)
|
||||
assertEquals(R.style.Typography_Body5, AudioContentCardSize.Medium.creatorStyleRes)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `small size matches figma contract`() {
|
||||
assertEquals(122, AudioContentCardSize.Small.cardWidthDp)
|
||||
assertEquals(122, AudioContentCardSize.Small.thumbnailSizeDp)
|
||||
assertEquals(114, AudioContentCardSize.Small.labelWidthDp)
|
||||
assertEquals(8, AudioContentCardSize.Small.thumbnailLabelGapDp)
|
||||
assertEquals(R.style.Typography_Body1, AudioContentCardSize.Small.titleStyleRes)
|
||||
assertEquals(R.style.Typography_Caption2, AudioContentCardSize.Small.creatorStyleRes)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user