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) + ) + } }