feat(widget): 오디오 콘텐츠 태그 배지를 추가한다
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Outline
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -15,7 +22,10 @@ class AudioContentCardView @JvmOverloads constructor(
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private var thumbnailContainer: FrameLayout? = null
|
||||
private var thumbnail: ImageView? = null
|
||||
private var topTagContainer: LinearLayout? = null
|
||||
private var bottomTagContainer: LinearLayout? = null
|
||||
private var labelContainer: LinearLayout? = null
|
||||
private var titleText: TextView? = null
|
||||
private var creatorText: TextView? = null
|
||||
@@ -26,21 +36,31 @@ class AudioContentCardView @JvmOverloads constructor(
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
thumbnailContainer = findViewById(R.id.fl_audio_content_thumbnail_container)
|
||||
thumbnail = findViewById(R.id.iv_audio_content_thumbnail)
|
||||
topTagContainer = findViewById(R.id.ll_audio_content_tag_top)
|
||||
bottomTagContainer = findViewById(R.id.ll_audio_content_tag_bottom)
|
||||
labelContainer = findViewById(R.id.ll_audio_content_label)
|
||||
titleText = findViewById(R.id.tv_audio_content_title)
|
||||
creatorText = findViewById(R.id.tv_audio_content_creator)
|
||||
if (isInEditMode && !hasRequiredViews()) return
|
||||
setThumbnailOutline()
|
||||
setSize(AudioContentCardSize.Medium)
|
||||
}
|
||||
|
||||
fun setSize(size: AudioContentCardSize) {
|
||||
updateRootWidth(size.cardWidthDp.dpToPx())
|
||||
|
||||
requireNotNull(thumbnail).layoutParams = LayoutParams(
|
||||
requireNotNull(thumbnailContainer).layoutParams = LayoutParams(
|
||||
size.thumbnailSizeDp.dpToPx(),
|
||||
size.thumbnailSizeDp.dpToPx()
|
||||
)
|
||||
|
||||
requireNotNull(thumbnail).layoutParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
requireNotNull(labelContainer).layoutParams = LayoutParams(
|
||||
size.labelWidthDp.dpToPx(),
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
@@ -70,6 +90,115 @@ class AudioContentCardView @JvmOverloads constructor(
|
||||
|
||||
fun thumbnailView(): ImageView = requireNotNull(thumbnail)
|
||||
|
||||
fun setTags(tags: Set<AudioContentTag>) {
|
||||
renderTags(requireNotNull(topTagContainer), orderedAudioContentTopTags(tags))
|
||||
renderTags(requireNotNull(bottomTagContainer), orderedAudioContentBottomTags(tags))
|
||||
}
|
||||
|
||||
private fun renderTags(
|
||||
container: LinearLayout,
|
||||
tags: List<AudioContentTag>
|
||||
) {
|
||||
container.removeAllViews()
|
||||
container.visibility = if (tags.isEmpty()) GONE else VISIBLE
|
||||
tags.forEach { tag ->
|
||||
container.addView(createTagView(tag))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTagView(tag: AudioContentTag): View = when (tag) {
|
||||
AudioContentTag.Original -> createIconTag(R.drawable.ic_content_tag_original)
|
||||
AudioContentTag.Point -> createIconTag(R.drawable.ic_content_tag_point)
|
||||
AudioContentTag.First -> createFirstTag()
|
||||
AudioContentTag.Free -> createFreeTag()
|
||||
}
|
||||
|
||||
private fun createIconTag(drawableResId: Int): ImageView {
|
||||
return ImageView(context).apply {
|
||||
setImageResource(drawableResId)
|
||||
contentDescription = null
|
||||
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
layoutParams = LinearLayout.LayoutParams(TAG_HEIGHT_DP.dpToPx(), TAG_HEIGHT_DP.dpToPx())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFirstTag(): LinearLayout {
|
||||
return LinearLayout(context).apply {
|
||||
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())
|
||||
addView(createFirstStarView())
|
||||
addView(createFirstTextView())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFirstStarView(): ImageView {
|
||||
return ImageView(context).apply {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFirstTextView(): TextView {
|
||||
return TextView(context).apply {
|
||||
text = FIRST_TEXT
|
||||
typeface = ResourcesCompat.getFont(context, R.font.phosphate_solid)
|
||||
setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
textSize = 16f
|
||||
isSingleLine = true
|
||||
includeFontPadding = false
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
marginStart = 1.dpToPx()
|
||||
topMargin = 2.dpToPx()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFreeTag(): TextView {
|
||||
return TextView(context).apply {
|
||||
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
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setThumbnailOutline() {
|
||||
requireNotNull(thumbnailContainer).apply {
|
||||
clipToOutline = true
|
||||
outlineProvider = object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
outline.setRoundRect(0, 0, view.width, view.height, resources.getDimension(R.dimen.radius_14))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasRequiredViews(): Boolean {
|
||||
return thumbnailContainer != null &&
|
||||
thumbnail != null &&
|
||||
topTagContainer != null &&
|
||||
bottomTagContainer != null &&
|
||||
labelContainer != null &&
|
||||
titleText != null &&
|
||||
creatorText != null
|
||||
}
|
||||
|
||||
private fun updateRootWidth(width: Int) {
|
||||
val currentLayoutParams = layoutParams
|
||||
layoutParams = if (currentLayoutParams == null) {
|
||||
@@ -86,5 +215,11 @@ 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_TEXT = "FIRST"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget
|
||||
|
||||
enum class AudioContentTag {
|
||||
Original,
|
||||
First,
|
||||
Point,
|
||||
Free
|
||||
}
|
||||
|
||||
fun Collection<AudioContentTag>.audioContentTopTags(): List<AudioContentTag> = orderedBy(
|
||||
AudioContentTag.Original,
|
||||
AudioContentTag.First
|
||||
)
|
||||
|
||||
fun Collection<AudioContentTag>.audioContentBottomTags(): List<AudioContentTag> = orderedBy(
|
||||
AudioContentTag.Point,
|
||||
AudioContentTag.Free
|
||||
)
|
||||
|
||||
fun orderedAudioContentTopTags(tags: Collection<AudioContentTag>): List<AudioContentTag> = tags.audioContentTopTags()
|
||||
|
||||
fun orderedAudioContentBottomTags(tags: Collection<AudioContentTag>): List<AudioContentTag> = tags.audioContentBottomTags()
|
||||
|
||||
private fun Collection<AudioContentTag>.orderedBy(
|
||||
vararg orderedTags: AudioContentTag
|
||||
): List<AudioContentTag> {
|
||||
val includedTags = toSet()
|
||||
return orderedTags.filter { it in includedTags }
|
||||
}
|
||||
BIN
app/src/main/res/drawable-mdpi/ic_content_tag_first_star.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_content_tag_first_star.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 324 B |
BIN
app/src/main/res/drawable-mdpi/ic_content_tag_original.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_content_tag_original.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 549 B |
BIN
app/src/main/res/drawable-mdpi/ic_content_tag_point.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_content_tag_point.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 750 B |
4
app/src/main/res/drawable/bg_audio_content_tag_first.xml
Normal file
4
app/src/main/res/drawable/bg_audio_content_tag_first.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#FF34B8" />
|
||||
</shape>
|
||||
4
app/src/main/res/drawable/bg_audio_content_tag_free.xml
Normal file
4
app/src/main/res/drawable/bg_audio_content_tag_free.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#052742" />
|
||||
</shape>
|
||||
@@ -5,13 +5,92 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_audio_content_thumbnail"
|
||||
<FrameLayout
|
||||
android:id="@+id/fl_audio_content_thumbnail_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_audio_content_card_thumbnail"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop" />
|
||||
android:background="@drawable/bg_audio_content_card_thumbnail">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_audio_content_thumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_audio_content_tag_top"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/spacing_24"
|
||||
android:layout_gravity="top|start"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/spacing_24"
|
||||
android:layout_height="@dimen/spacing_24"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_content_tag_original" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="62dp"
|
||||
android:layout_height="@dimen/spacing_24"
|
||||
android:background="@drawable/bg_audio_content_tag_first"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="17dp"
|
||||
android:layout_height="17dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_content_tag_first_star" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:fontFamily="@font/phosphate_solid"
|
||||
android:includeFontPadding="false"
|
||||
android:singleLine="true"
|
||||
android:text="FIRST"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_audio_content_tag_bottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/spacing_24"
|
||||
android:layout_gravity="bottom|start"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/spacing_24"
|
||||
android:layout_height="@dimen/spacing_24"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_content_tag_point" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/spacing_24"
|
||||
android:background="@drawable/bg_audio_content_tag_free"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="34dp"
|
||||
android:paddingHorizontal="6dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/audio_content_tag_free"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_audio_content_label"
|
||||
|
||||
@@ -1161,6 +1161,7 @@ The upload will continue even if you leave this page.</string>
|
||||
<string name="audio_content_label_all">All</string>
|
||||
<string name="audio_content_total_unit">items</string>
|
||||
<string name="audio_content_price_free">Free</string>
|
||||
<string name="audio_content_tag_free">Free</string>
|
||||
<string name="audio_content_badge_scheduled">Coming soon</string>
|
||||
<string name="audio_content_badge_point">Points</string>
|
||||
<string name="audio_content_badge_owned">Owned</string>
|
||||
|
||||
@@ -1159,6 +1159,7 @@
|
||||
<string name="audio_content_label_all">全</string>
|
||||
<string name="audio_content_total_unit">個</string>
|
||||
<string name="audio_content_price_free">無料</string>
|
||||
<string name="audio_content_tag_free">無料</string>
|
||||
<string name="audio_content_badge_scheduled">公開予定</string>
|
||||
<string name="audio_content_badge_point">ポイント</string>
|
||||
<string name="audio_content_badge_owned">所持中</string>
|
||||
|
||||
@@ -1158,6 +1158,7 @@
|
||||
<string name="audio_content_label_all">전체</string>
|
||||
<string name="audio_content_total_unit">개</string>
|
||||
<string name="audio_content_price_free">무료</string>
|
||||
<string name="audio_content_tag_free">무료</string>
|
||||
<string name="audio_content_badge_scheduled">오픈예정</string>
|
||||
<string name="audio_content_badge_point">포인트</string>
|
||||
<string name="audio_content_badge_owned">소장중</string>
|
||||
|
||||
Reference in New Issue
Block a user