diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt
index b647639e..1dd498ce 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt
@@ -115,6 +115,11 @@ class AudioContentCardView @JvmOverloads constructor(
private fun createIconTag(drawableResId: Int): ImageView {
return ImageView(context).apply {
+ id = when (drawableResId) {
+ R.drawable.ic_content_tag_original -> R.id.iv_audio_content_tag_original
+ R.drawable.ic_content_tag_point -> R.id.iv_audio_content_tag_point
+ else -> View.NO_ID
+ }
setImageResource(drawableResId)
contentDescription = null
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
@@ -124,9 +129,20 @@ class AudioContentCardView @JvmOverloads constructor(
private fun createFirstTag(): LinearLayout {
return LinearLayout(context).apply {
+ id = R.id.ll_audio_content_tag_first
orientation = HORIZONTAL
background = ContextCompat.getDrawable(context, R.drawable.bg_audio_content_tag_first)
- layoutParams = LinearLayout.LayoutParams(FIRST_TAG_WIDTH_DP.dpToPx(), TAG_HEIGHT_DP.dpToPx())
+ gravity = Gravity.CENTER
+ setPadding(
+ FIRST_TAG_PADDING_DP.dpToPx(),
+ FIRST_TAG_PADDING_DP.dpToPx(),
+ FIRST_TAG_PADDING_DP.dpToPx(),
+ FIRST_TAG_PADDING_DP.dpToPx()
+ )
+ layoutParams = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ TAG_HEIGHT_DP.dpToPx()
+ )
addView(createFirstStarView())
addView(createFirstTextView())
}
@@ -137,10 +153,7 @@ class AudioContentCardView @JvmOverloads constructor(
setImageResource(R.drawable.ic_content_tag_first_star)
contentDescription = null
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
- layoutParams = LinearLayout.LayoutParams(FIRST_STAR_SIZE_DP.dpToPx(), FIRST_STAR_SIZE_DP.dpToPx()).apply {
- marginStart = 2.dpToPx()
- topMargin = 4.dpToPx()
- }
+ layoutParams = LinearLayout.LayoutParams(FIRST_STAR_SIZE_DP.dpToPx(), FIRST_STAR_SIZE_DP.dpToPx())
}
}
@@ -151,29 +164,27 @@ class AudioContentCardView @JvmOverloads constructor(
setTextColor(ContextCompat.getColor(context, R.color.white))
textSize = 16f
isSingleLine = true
- includeFontPadding = false
+ includeFontPadding = true
gravity = Gravity.CENTER_VERTICAL
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
- marginStart = 1.dpToPx()
- topMargin = 2.dpToPx()
+ marginStart = FIRST_TEXT_MARGIN_START_DP.dpToPx()
}
}
}
private fun createFreeTag(): TextView {
return TextView(context).apply {
+ id = R.id.tv_audio_content_tag_free
text = context.getString(R.string.audio_content_tag_free)
background = ContextCompat.getDrawable(context, R.drawable.bg_audio_content_tag_free)
setTextColor(ContextCompat.getColor(context, R.color.white))
- textSize = 14f
+ setTextAppearance(R.style.Typography_Body4)
gravity = Gravity.CENTER
isSingleLine = true
includeFontPadding = false
- minWidth = FREE_TAG_MIN_WIDTH_DP.dpToPx()
- setPadding(FREE_TAG_HORIZONTAL_PADDING_DP.dpToPx(), 0, FREE_TAG_HORIZONTAL_PADDING_DP.dpToPx(), 0)
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, TAG_HEIGHT_DP.dpToPx())
}
}
@@ -216,10 +227,9 @@ class AudioContentCardView @JvmOverloads constructor(
private companion object {
const val TITLE_CREATOR_GAP_DP = 2
const val TAG_HEIGHT_DP = 24
- const val FIRST_TAG_WIDTH_DP = 62
const val FIRST_STAR_SIZE_DP = 17
- const val FREE_TAG_MIN_WIDTH_DP = 34
- const val FREE_TAG_HORIZONTAL_PADDING_DP = 6
+ const val FIRST_TAG_PADDING_DP = 4
+ const val FIRST_TEXT_MARGIN_START_DP = 2
const val FIRST_TEXT = "FIRST"
}
}
diff --git a/app/src/main/res/drawable/bg_audio_content_tag_free.xml b/app/src/main/res/drawable/bg_audio_content_tag_free.xml
index 15c6e6f6..591f9cf3 100644
--- a/app/src/main/res/drawable/bg_audio_content_tag_free.xml
+++ b/app/src/main/res/drawable/bg_audio_content_tag_free.xml
@@ -1,4 +1,7 @@
+
diff --git a/app/src/main/res/layout/view_audio_content_card.xml b/app/src/main/res/layout/view_audio_content_card.xml
index 09db9df5..d25b2b01 100644
--- a/app/src/main/res/layout/view_audio_content_card.xml
+++ b/app/src/main/res/layout/view_audio_content_card.xml
@@ -21,39 +21,40 @@
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:paddingHorizontal="4dp">
+ android:textColor="@color/white" />
diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
index 396afd96..d62492fd 100644
--- a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
+++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
@@ -15,9 +15,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import kr.co.vividnext.sodalive.R
+import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationFirstAudioContentUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel
+import kr.co.vividnext.sodalive.v2.main.home.ui.HomeFirstAudioAdapter
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeLiveAdapter
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentDebutCreatorAdapter
+import kr.co.vividnext.sodalive.v2.widget.AudioContentCardView
+import kr.co.vividnext.sodalive.v2.widget.AudioContentTag
import kr.co.vividnext.sodalive.v2.widget.TextTabBarView
import kr.co.vividnext.sodalive.v2.widget.banner.BannerView
import org.junit.Assert.assertEquals
@@ -146,6 +150,131 @@ class HomeMainFragmentLayoutTest {
assertEquals(4.dpToPx(), layoutParams.marginEnd)
}
+ @Test
+ fun `first audio item matches figma card dimensions`() {
+ val firstAudio = inflateViewWithParent(R.layout.item_home_first_audio_content)
+ val thumbnailContainer = firstAudio.findViewById(R.id.fl_home_first_audio_thumbnail_container)
+ val thumbnail = firstAudio.findViewById(R.id.iv_home_first_audio_thumbnail)
+ val creatorProfile = firstAudio.findViewById(R.id.iv_home_first_audio_creator_profile)
+ val creatorName = firstAudio.findViewById(R.id.tv_home_first_audio_creator_nickname)
+
+ assertEquals(185.dpToPx(), firstAudio.layoutParams.width)
+ assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, firstAudio.layoutParams.height)
+ assertEquals(185.dpToPx(), thumbnail.layoutParams.width)
+ assertEquals(185.dpToPx(), thumbnail.layoutParams.height)
+ assertNotNull(thumbnailContainer)
+ assertEquals(42.dpToPx(), creatorProfile.layoutParams.width)
+ assertEquals(42.dpToPx(), creatorProfile.layoutParams.height)
+ assertEquals(14f, creatorName.textSize / creatorName.resources.displayMetrics.scaledDensity)
+ }
+
+ @Test
+ fun `home first audio section matches figma list spacing`() {
+ val root = inflateView(R.layout.fragment_v2_main_home)
+ val firstAudioList = root.findViewById(R.id.rv_home_first_audio_contents)
+ val context = ApplicationProvider.getApplicationContext()
+ val parent = RecyclerView(context)
+ parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
+ val viewHolder = HomeFirstAudioAdapter().onCreateViewHolder(parent, 0)
+ val layoutParams = viewHolder.itemView.layoutParams as ViewGroup.MarginLayoutParams
+
+ assertEquals(14.dpToPx(), firstAudioList.paddingStart)
+ assertEquals(14.dpToPx(), (firstAudioList.layoutParams as ViewGroup.MarginLayoutParams).topMargin)
+ assertEquals(4.dpToPx(), layoutParams.marginEnd)
+ }
+
+ @Test
+ fun `first audio adapter clips thumbnail container`() {
+ val context = ApplicationProvider.getApplicationContext()
+ val parent = RecyclerView(context)
+ parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
+ val viewHolder = HomeFirstAudioAdapter().onCreateViewHolder(parent, 0)
+ val thumbnailContainer = viewHolder.itemView.findViewById(R.id.fl_home_first_audio_thumbnail_container)
+
+ assertEquals(true, thumbnailContainer.clipToOutline)
+ assertNotNull(thumbnailContainer.outlineProvider)
+ }
+
+ @Test
+ fun `first audio adapter binds tag visibility`() {
+ val context = ApplicationProvider.getApplicationContext()
+ val parent = RecyclerView(context)
+ parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
+ val adapter = HomeFirstAudioAdapter()
+ adapter.submitItems(listOf(firstAudioItem()))
+ val viewHolder = adapter.onCreateViewHolder(parent, 0)
+
+ adapter.onBindViewHolder(viewHolder, 0)
+
+ assertEquals(View.VISIBLE, viewHolder.itemView.findViewById(R.id.ll_home_first_audio_tag_top).visibility)
+ assertEquals(View.VISIBLE, viewHolder.itemView.findViewById(R.id.ll_home_first_audio_tag_bottom).visibility)
+ assertEquals(View.VISIBLE, viewHolder.itemView.findViewById(R.id.ll_home_first_audio_tag_first).visibility)
+ assertEquals(View.VISIBLE, viewHolder.itemView.findViewById(R.id.iv_home_first_audio_tag_point).visibility)
+ assertEquals(View.VISIBLE, viewHolder.itemView.findViewById(R.id.tv_home_first_audio_tag_free).visibility)
+ }
+
+ @Test
+ fun `first audio adapter clears nullable images`() {
+ val context = ApplicationProvider.getApplicationContext()
+ val parent = RecyclerView(context)
+ parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
+ val adapter = HomeFirstAudioAdapter()
+ adapter.submitItems(listOf(firstAudioItem()))
+ val viewHolder = adapter.onCreateViewHolder(parent, 0)
+ val thumbnail = viewHolder.itemView.findViewById(R.id.iv_home_first_audio_thumbnail)
+ val creatorProfile = viewHolder.itemView.findViewById(R.id.iv_home_first_audio_creator_profile)
+ thumbnail.setImageResource(R.drawable.ic_launcher_background)
+ creatorProfile.setImageResource(R.drawable.ic_launcher_background)
+
+ adapter.onBindViewHolder(viewHolder, 0)
+
+ assertEquals(null, thumbnail.drawable)
+ assertEquals(null, creatorProfile.drawable)
+ }
+
+ @Test
+ fun `audio content card clips image area and overlay tags together`() {
+ val card = inflateView(R.layout.view_audio_content_card) as AudioContentCardView
+ val thumbnailContainer = card.findViewById(R.id.fl_audio_content_thumbnail_container)
+
+ assertEquals(true, thumbnailContainer.clipToOutline)
+ assertNotNull(thumbnailContainer.outlineProvider)
+ }
+
+ @Test
+ fun `audio content card tag attributes match first audio item`() {
+ val card = inflateView(R.layout.view_audio_content_card) as AudioContentCardView
+ val firstAudio = inflateView(R.layout.item_home_first_audio_content)
+ card.setTags(setOf(AudioContentTag.Original, AudioContentTag.First, AudioContentTag.Point, AudioContentTag.Free))
+ val topTagContainer = card.findViewById(R.id.ll_audio_content_tag_top)
+ val bottomTagContainer = card.findViewById(R.id.ll_audio_content_tag_bottom)
+ val expectedFirstTag = firstAudio.findViewById(R.id.ll_home_first_audio_tag_first)
+ val expectedFreeTag = firstAudio.findViewById(R.id.tv_home_first_audio_tag_free)
+ val originalTag = card.findViewById(R.id.iv_audio_content_tag_original)
+ val firstTag = card.findViewById(R.id.ll_audio_content_tag_first)
+ val firstIcon = firstTag.getChildAt(0) as ImageView
+ val firstText = firstTag.getChildAt(1) as TextView
+ val pointTag = card.findViewById(R.id.iv_audio_content_tag_point)
+ val freeTag = card.findViewById(R.id.tv_audio_content_tag_free)
+
+ assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, topTagContainer.layoutParams.height)
+ assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, bottomTagContainer.layoutParams.height)
+ assertEquals(24.dpToPx(), originalTag.layoutParams.width)
+ assertEquals(24.dpToPx(), originalTag.layoutParams.height)
+ assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, firstTag.layoutParams.width)
+ assertEquals(expectedFirstTag.layoutParams.height, firstTag.layoutParams.height)
+ assertEquals(expectedFirstTag.paddingStart, firstTag.paddingStart)
+ assertEquals(expectedFirstTag.paddingTop, firstTag.paddingTop)
+ assertEquals(17.dpToPx(), firstIcon.layoutParams.width)
+ assertEquals(17.dpToPx(), firstIcon.layoutParams.height)
+ assertEquals(2.dpToPx(), (firstText.layoutParams as ViewGroup.MarginLayoutParams).marginStart)
+ assertEquals(true, firstText.includeFontPadding)
+ assertEquals(24.dpToPx(), pointTag.layoutParams.width)
+ assertEquals(24.dpToPx(), pointTag.layoutParams.height)
+ assertEquals(expectedFreeTag.layoutParams.height, freeTag.layoutParams.height)
+ assertEquals(expectedFreeTag.paddingStart, freeTag.paddingStart)
+ }
+
@Test
fun `popular community section is hidden until phase7 binding is implemented`() {
val root = inflateView(R.layout.fragment_v2_main_home)
@@ -289,4 +418,18 @@ class HomeMainFragmentLayoutTest {
beginDateTime = null
)
}
+
+ private fun firstAudioItem(): HomeRecommendationFirstAudioContentUiModel {
+ return HomeRecommendationFirstAudioContentUiModel(
+ contentId = 1L,
+ creatorId = 1L,
+ creatorNickname = "크리에이터 이름",
+ creatorProfileImage = null,
+ title = "콘텐츠 제목",
+ price = 0,
+ coverImage = null,
+ releaseDate = "",
+ tags = setOf(AudioContentTag.First, AudioContentTag.Point, AudioContentTag.Free)
+ )
+ }
}