feat(widget): 라이브 썸네일 컴포넌트를 추가한다

This commit is contained in:
2026-05-20 17:55:19 +09:00
parent 960e78afac
commit c58f03be08
18 changed files with 1422 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
package kr.co.vividnext.sodalive.v2.widget.livethumbnail
import android.content.Context
import android.graphics.Outline
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import kr.co.vividnext.sodalive.R
class LiveThumbnailDetailView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private var image: ImageView? = null
private var liveStartTimeText: TextView? = null
private var titleText: TextView? = null
private var creatorText: TextView? = null
private var currentItem: LiveThumbnailItem? = null
private var clickListener: ((LiveThumbnailItem) -> Unit)? = null
override fun onFinishInflate() {
super.onFinishInflate()
image = findViewById(R.id.iv_live_thumbnail_image)
liveStartTimeText = findViewById(R.id.tv_live_thumbnail_start_time)
titleText = findViewById(R.id.tv_live_thumbnail_title)
creatorText = findViewById(R.id.tv_live_thumbnail_creator)
imageView().clipToOutline = true
imageView().outlineProvider = circleOutlineProvider()
}
fun bind(item: LiveThumbnailItem) {
currentItem = item
requireNotNull(liveStartTimeText).text = item.liveStartTimeText
requireNotNull(titleText).text = item.title
requireNotNull(creatorText).text = item.creatorName
applyClickState(item)
}
fun imageView(): ImageView = requireNotNull(image)
fun setOnLiveThumbnailClick(listener: ((LiveThumbnailItem) -> Unit)?) {
clickListener = listener
currentItem?.let(::applyClickState)
}
private fun applyClickState(item: LiveThumbnailItem) {
isClickable = clickListener != null
setOnClickListener(if (isClickable) View.OnClickListener { clickListener?.invoke(item) } else null)
}
private fun circleOutlineProvider() = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setOval(0, 0, view.width, view.height)
}
}
}

View File

@@ -0,0 +1,10 @@
package kr.co.vividnext.sodalive.v2.widget.livethumbnail
data class LiveThumbnailItem(
val liveId: Long,
val creatorId: Long,
val imageUrl: String,
val title: String,
val creatorName: String,
val liveStartTimeText: String
)

View File

@@ -0,0 +1,55 @@
package kr.co.vividnext.sodalive.v2.widget.livethumbnail
import android.content.Context
import android.graphics.Outline
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import kr.co.vividnext.sodalive.R
class LiveThumbnailSimpleView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private var image: ImageView? = null
private var creatorText: TextView? = null
private var currentItem: LiveThumbnailItem? = null
private var clickListener: ((LiveThumbnailItem) -> Unit)? = null
override fun onFinishInflate() {
super.onFinishInflate()
image = findViewById(R.id.iv_live_thumbnail_image)
creatorText = findViewById(R.id.tv_live_thumbnail_creator)
imageView().clipToOutline = true
imageView().outlineProvider = circleOutlineProvider()
}
fun bind(item: LiveThumbnailItem) {
currentItem = item
requireNotNull(creatorText).text = item.creatorName
applyClickState(item)
}
fun imageView(): ImageView = requireNotNull(image)
fun setOnLiveThumbnailClick(listener: ((LiveThumbnailItem) -> Unit)?) {
clickListener = listener
currentItem?.let(::applyClickState)
}
private fun applyClickState(item: LiveThumbnailItem) {
isClickable = clickListener != null
setOnClickListener(if (isClickable) View.OnClickListener { clickListener?.invoke(item) } else null)
}
private fun circleOutlineProvider() = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setOval(0, 0, view.width, view.height)
}
}
}

View File

@@ -0,0 +1,28 @@
package kr.co.vividnext.sodalive.v2.widget.livethumbnail
data class LiveThumbnailSize(
val rootWidthDp: Int,
val rootHeightDp: Int?,
val profileAreaHeightDp: Int?,
val imageSizeDp: Int,
val textWidthDp: Int
) {
companion object {
fun from(variant: LiveThumbnailVariant): LiveThumbnailSize = when (variant) {
LiveThumbnailVariant.Simple -> LiveThumbnailSize(
rootWidthDp = 70,
rootHeightDp = null,
profileAreaHeightDp = 76,
imageSizeDp = 58,
textWidthDp = 70
)
LiveThumbnailVariant.Detail -> LiveThumbnailSize(
rootWidthDp = 266,
rootHeightDp = 99,
profileAreaHeightDp = null,
imageSizeDp = 75,
textWidthDp = 149
)
}
}
}

View File

@@ -0,0 +1,6 @@
package kr.co.vividnext.sodalive.v2.widget.livethumbnail
enum class LiveThumbnailVariant {
Simple,
Detail
}