feat(creator): 채널 홈 섹션 렌더링을 연결한다
This commit is contained in:
@@ -0,0 +1,616 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.creator.channel.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.HorizontalScrollView
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeSection
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class CreatorChannelHomeSectionAdapter(
|
||||||
|
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit = {}
|
||||||
|
) : RecyclerView.Adapter<CreatorChannelHomeSectionAdapter.SectionViewHolder>() {
|
||||||
|
|
||||||
|
private var items: List<CreatorChannelHomeSection> = emptyList()
|
||||||
|
|
||||||
|
fun submitItems(items: List<CreatorChannelHomeSection>) {
|
||||||
|
this.items = items
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int = items[position].layoutResId
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
|
return SectionViewHolder(view, onScheduleClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SectionViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
|
class SectionViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
private val title: TextView? = view.findViewById(R.id.tv_section_title)
|
||||||
|
private val sectionItems: LinearLayout? = view.findViewById(R.id.ll_section_items)
|
||||||
|
private val currentLiveTitle: TextView? = view.findViewById(R.id.tv_current_live_title)
|
||||||
|
private val currentLiveStartTime: TextView? = view.findViewById(R.id.tv_current_live_start_time)
|
||||||
|
private val currentLivePrice: TextView? = view.findViewById(R.id.tv_current_live_price)
|
||||||
|
private val currentLiveAdult: TextView? = view.findViewById(R.id.tv_current_live_adult)
|
||||||
|
private val currentLivePriceLayout: View? = view.findViewById(R.id.layout_current_live_price)
|
||||||
|
private val latestAudioThumbnail: ImageView? = view.findViewById(R.id.iv_latest_audio_thumbnail)
|
||||||
|
private val latestAudioPointTag: ImageView? = view.findViewById(R.id.iv_latest_audio_point_tag)
|
||||||
|
private val latestAudioTitle: TextView? = view.findViewById(R.id.tv_latest_audio_title)
|
||||||
|
private val latestAudioDuration: TextView? = view.findViewById(R.id.tv_latest_audio_duration)
|
||||||
|
private val donationItems: LinearLayout? = view.findViewById(R.id.ll_donation_items)
|
||||||
|
private val noticeItems: LinearLayout? = view.findViewById(R.id.ll_notice_items)
|
||||||
|
private val scheduleTimeline: LinearLayout? = view.findViewById(R.id.ll_schedule_timeline)
|
||||||
|
private val scheduleItems: LinearLayout? = view.findViewById(R.id.ll_schedule_items)
|
||||||
|
|
||||||
|
fun bind(item: CreatorChannelHomeSection) {
|
||||||
|
title?.setText(item.titleResId)
|
||||||
|
sectionItems?.removeAllViews()
|
||||||
|
donationItems?.removeAllViews()
|
||||||
|
noticeItems?.removeAllViews()
|
||||||
|
scheduleTimeline?.removeAllViews()
|
||||||
|
scheduleItems?.removeAllViews()
|
||||||
|
when (item) {
|
||||||
|
is CreatorChannelHomeSection.CurrentLive -> bindCurrentLive(item)
|
||||||
|
is CreatorChannelHomeSection.LatestAudioContent -> bindLatestAudioContent(item)
|
||||||
|
is CreatorChannelHomeSection.Donations -> bindDonations(item)
|
||||||
|
is CreatorChannelHomeSection.Notices -> bindNotices(item)
|
||||||
|
is CreatorChannelHomeSection.Schedules -> bindSchedules(item)
|
||||||
|
is CreatorChannelHomeSection.AudioContents -> bindAudioContents(item)
|
||||||
|
is CreatorChannelHomeSection.Series -> bindSeries(item)
|
||||||
|
is CreatorChannelHomeSection.Communities -> bindCommunities(item)
|
||||||
|
is CreatorChannelHomeSection.FanTalk -> bindFanTalk(item)
|
||||||
|
is CreatorChannelHomeSection.Introduce -> bindIntroduce(item)
|
||||||
|
is CreatorChannelHomeSection.Activity -> bindActivity(item)
|
||||||
|
is CreatorChannelHomeSection.Sns -> bindSns(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindCurrentLive(item: CreatorChannelHomeSection.CurrentLive) {
|
||||||
|
currentLiveTitle?.text = item.live.title
|
||||||
|
currentLiveStartTime?.text = item.live.beginDateTimeUtc
|
||||||
|
currentLivePrice?.text = item.live.price.toString()
|
||||||
|
currentLivePriceLayout?.isVisible = item.live.price > 0
|
||||||
|
currentLiveAdult?.isVisible = item.live.isAdult
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindLatestAudioContent(item: CreatorChannelHomeSection.LatestAudioContent) {
|
||||||
|
latestAudioTitle?.text = item.audioContent.title
|
||||||
|
latestAudioDuration?.text = item.audioContent.duration.orEmpty()
|
||||||
|
latestAudioPointTag?.isVisible = item.audioContent.isPointAvailable
|
||||||
|
latestAudioThumbnail?.loadUrl(item.audioContent.imageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindDonations(item: CreatorChannelHomeSection.Donations) {
|
||||||
|
val visibleDonations = item.donations.take(MAX_DONATION_ITEM_COUNT)
|
||||||
|
visibleDonations.forEachIndexed { index, donation ->
|
||||||
|
val row = LayoutInflater.from(itemView.context).inflate(
|
||||||
|
R.layout.item_creator_channel_home_donation_row,
|
||||||
|
donationItems,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
row.layoutParams = LinearLayout.LayoutParams(
|
||||||
|
calculateCreatorChannelDonationCardWidthDp(itemView.resources.configuration.screenWidthDp).dp(),
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
marginEnd = if (index == visibleDonations.lastIndex) 0 else 4.dp()
|
||||||
|
}
|
||||||
|
row.findViewById<View>(R.id.layout_donation_header)
|
||||||
|
.setBackgroundColor(itemView.context.getColor(calculateCreatorChannelDonationHeaderColorRes(donation.can)))
|
||||||
|
row.findViewById<ImageView>(R.id.iv_donation_profile).loadUrl(donation.profileImageUrl) {
|
||||||
|
placeholder(R.drawable.ic_placeholder_profile)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
row.findViewById<TextView>(R.id.tv_donation_nickname).text = donation.nickname
|
||||||
|
row.findViewById<TextView>(R.id.tv_donation_created_at).text = donation.createdAtUtc
|
||||||
|
row.findViewById<TextView>(R.id.tv_donation_can).text = itemView.context.getString(
|
||||||
|
R.string.creator_channel_donation_can_format,
|
||||||
|
donation.can.moneyFormat()
|
||||||
|
)
|
||||||
|
row.findViewById<TextView>(R.id.tv_donation_message).text = donation.message.ifBlank {
|
||||||
|
itemView.context.getString(R.string.creator_channel_donation_fallback_message, donation.can)
|
||||||
|
}
|
||||||
|
donationItems?.addView(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindNotices(item: CreatorChannelHomeSection.Notices) {
|
||||||
|
val visibleNotices = item.notices.take(MAX_NOTICE_ITEM_COUNT)
|
||||||
|
visibleNotices.forEachIndexed { index, notice ->
|
||||||
|
val row = LayoutInflater.from(itemView.context).inflate(
|
||||||
|
R.layout.item_creator_channel_home_notice_row,
|
||||||
|
noticeItems,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
row.layoutParams = LinearLayout.LayoutParams(
|
||||||
|
calculateCreatorChannelNoticeCardWidthDp(itemView.resources.configuration.screenWidthDp).dp(),
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
marginEnd = if (index == visibleNotices.lastIndex) 0 else 4.dp()
|
||||||
|
}
|
||||||
|
row.findViewById<ImageView>(R.id.iv_notice_profile).loadUrl(notice.creatorProfileUrl) {
|
||||||
|
placeholder(R.drawable.ic_placeholder_profile)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
row.findViewById<TextView>(R.id.tv_notice_creator_name).text = notice.creatorNickname
|
||||||
|
row.findViewById<TextView>(R.id.tv_notice_created_at).text = notice.dateUtc
|
||||||
|
row.findViewById<TextView>(R.id.tv_notice_content).text = notice.content
|
||||||
|
val noticeThumbnail = row.findViewById<ImageView>(R.id.iv_notice_thumbnail)
|
||||||
|
noticeThumbnail.isVisible = !notice.imageUrl.isNullOrBlank()
|
||||||
|
notice.imageUrl?.let(noticeThumbnail::loadUrl)
|
||||||
|
noticeItems?.addView(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindSchedules(item: CreatorChannelHomeSection.Schedules) {
|
||||||
|
val visibleSchedules = item.schedules.take(MAX_SCHEDULE_ITEM_COUNT)
|
||||||
|
bindScheduleTimeline(visibleSchedules.size)
|
||||||
|
visibleSchedules.forEachIndexed { index, schedule ->
|
||||||
|
val row = LayoutInflater.from(itemView.context).inflate(
|
||||||
|
R.layout.item_creator_channel_home_schedule_row,
|
||||||
|
scheduleItems,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
row.layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
bottomMargin = if (index == visibleSchedules.lastIndex) 0 else 4.dp()
|
||||||
|
}
|
||||||
|
row.findViewById<TextView>(R.id.tv_schedule_date).text =
|
||||||
|
formatCreatorChannelScheduleDate(schedule.scheduledAtUtc)
|
||||||
|
row.findViewById<TextView>(R.id.tv_schedule_day_of_week).text =
|
||||||
|
formatCreatorChannelScheduleDayOfWeek(schedule.scheduledAtUtc)
|
||||||
|
row.findViewById<TextView>(R.id.tv_schedule_title).text = schedule.title
|
||||||
|
row.findViewById<TextView>(R.id.tv_schedule_type).text = itemView.context.getString(schedule.type.labelResId)
|
||||||
|
row.findViewById<TextView>(R.id.tv_schedule_time).text =
|
||||||
|
formatCreatorChannelScheduleTime(schedule.scheduledAtUtc)
|
||||||
|
row.setOnClickListener { onScheduleClick(schedule) }
|
||||||
|
scheduleItems?.addView(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindScheduleTimeline(count: Int) {
|
||||||
|
repeat(count) { index ->
|
||||||
|
scheduleTimeline?.addView(createScheduleTimelineDot(index == 0))
|
||||||
|
if (index < calculateCreatorChannelScheduleTimelineLineCount(count)) {
|
||||||
|
scheduleTimeline?.addView(createScheduleTimelineLine())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createScheduleTimelineDot(isFirst: Boolean): View = View(itemView.context).apply {
|
||||||
|
setBackgroundResource(R.drawable.bg_creator_channel_schedule_timeline_dot)
|
||||||
|
if (isFirst) {
|
||||||
|
background.setTint(itemView.context.getColor(R.color.soda_400))
|
||||||
|
}
|
||||||
|
layoutParams = LinearLayout.LayoutParams(8.dp(), 8.dp()).apply {
|
||||||
|
topMargin = if (isFirst) 37.dp() else 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createScheduleTimelineLine(): View = View(itemView.context).apply {
|
||||||
|
setBackgroundResource(R.drawable.bg_creator_channel_schedule_timeline_line)
|
||||||
|
layoutParams = LinearLayout.LayoutParams(2.dp(), 63.dp()).apply {
|
||||||
|
gravity = Gravity.CENTER_HORIZONTAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindAudioContents(item: CreatorChannelHomeSection.AudioContents) {
|
||||||
|
val row = createHorizontalRow()
|
||||||
|
item.audioContents.forEach { audioContent ->
|
||||||
|
row.addView(createAudioTile(audioContent))
|
||||||
|
}
|
||||||
|
sectionItems?.addView(createHorizontalScrollRow(row))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindSeries(item: CreatorChannelHomeSection.Series) {
|
||||||
|
val row = createHorizontalRow()
|
||||||
|
item.series.forEach { series ->
|
||||||
|
row.addView(
|
||||||
|
createContentTile(
|
||||||
|
title = series.title,
|
||||||
|
body = itemView.context.getString(
|
||||||
|
R.string.creator_channel_series_summary,
|
||||||
|
series.numberOfContent,
|
||||||
|
series.publishedDaysOfWeek
|
||||||
|
),
|
||||||
|
imageUrl = series.coverImageUrl,
|
||||||
|
imageWidth = 163.dp(),
|
||||||
|
imageHeight = 230.dp()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sectionItems?.addView(createHorizontalScrollRow(row))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindCommunities(item: CreatorChannelHomeSection.Communities) {
|
||||||
|
item.communities.forEach { community ->
|
||||||
|
addFeedCard(
|
||||||
|
title = community.content,
|
||||||
|
body = itemView.context.getString(
|
||||||
|
R.string.creator_channel_community_summary,
|
||||||
|
community.creatorNickname,
|
||||||
|
community.likeCount,
|
||||||
|
community.commentCount
|
||||||
|
),
|
||||||
|
imageUrl = community.imageUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindFanTalk(item: CreatorChannelHomeSection.FanTalk) {
|
||||||
|
item.fanTalk.latestFanTalk?.let { fanTalk ->
|
||||||
|
addCommentCard(
|
||||||
|
title = fanTalk.content,
|
||||||
|
body = itemView.context.getString(
|
||||||
|
R.string.creator_channel_fantalk_summary,
|
||||||
|
fanTalk.nickname,
|
||||||
|
item.fanTalk.totalCount
|
||||||
|
),
|
||||||
|
imageUrl = fanTalk.profileImageUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindIntroduce(item: CreatorChannelHomeSection.Introduce) {
|
||||||
|
addTextCard(title = item.introduce, body = "", imageUrl = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindActivity(item: CreatorChannelHomeSection.Activity) {
|
||||||
|
val activity = item.activity
|
||||||
|
addActivityRow(
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_debut),
|
||||||
|
formatDebutActivityValue(activity.debutDateUtc, activity.dDay)
|
||||||
|
)
|
||||||
|
addActivityRow(
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_live_count),
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_live_count_format, activity.liveCount)
|
||||||
|
)
|
||||||
|
addActivityRow(
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_live_duration),
|
||||||
|
itemView.context.getString(
|
||||||
|
R.string.creator_channel_activity_live_duration_format,
|
||||||
|
activity.liveDurationHours
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addActivityRow(
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_live_contributor),
|
||||||
|
itemView.context.getString(
|
||||||
|
R.string.creator_channel_activity_live_contributor_format,
|
||||||
|
activity.liveContributorCount
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addActivityRow(
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_audio_count),
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_audio_count_format, activity.audioContentCount)
|
||||||
|
)
|
||||||
|
addActivityRow(
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_series_count),
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_series_count_format, activity.seriesCount)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindSns(item: CreatorChannelHomeSection.Sns) {
|
||||||
|
val row = LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
}
|
||||||
|
item.items.forEachIndexed { index, sns ->
|
||||||
|
row.addView(
|
||||||
|
createSnsButton(
|
||||||
|
iconResId = sns.iconResId,
|
||||||
|
url = sns.url,
|
||||||
|
isLast = index == item.items.lastIndex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sectionItems?.addView(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAudioTile(audioContent: CreatorChannelAudioContentResponse): LinearLayout =
|
||||||
|
createContentTile(
|
||||||
|
title = audioContent.title,
|
||||||
|
body = listOfNotNull(audioContent.seriesName, audioContent.duration).joinToText(),
|
||||||
|
imageUrl = audioContent.imageUrl,
|
||||||
|
imageWidth = 122.dp(),
|
||||||
|
imageHeight = 122.dp()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun addTextCard(title: String, body: String, imageUrl: String?) {
|
||||||
|
val card = LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
gravity = android.view.Gravity.CENTER_VERTICAL
|
||||||
|
setPadding(14.dp(), 14.dp(), 14.dp(), 14.dp())
|
||||||
|
setBackgroundResource(R.drawable.bg_round_corner_16_7_222222)
|
||||||
|
layoutParams = defaultBlockLayoutParams()
|
||||||
|
}
|
||||||
|
card.addView(createImage(imageUrl, 120.dp(), 80.dp()))
|
||||||
|
card.addView(createTextGroup(title, body, bodyMaxLines = 4))
|
||||||
|
sectionItems?.addView(card)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFeedCard(title: String, body: String, imageUrl: String?) {
|
||||||
|
val card = LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
setPadding(14.dp(), 14.dp(), 14.dp(), 14.dp())
|
||||||
|
setBackgroundResource(R.drawable.bg_round_corner_16_7_222222)
|
||||||
|
layoutParams = defaultBlockLayoutParams()
|
||||||
|
}
|
||||||
|
card.addView(createText(body, R.style.Typography_Caption2, R.color.gray_500, maxLines = 1))
|
||||||
|
card.addView(createText(title, R.style.Typography_Body2, R.color.white, maxLines = 4))
|
||||||
|
card.addView(createImage(imageUrl, LinearLayout.LayoutParams.MATCH_PARENT, 180.dp()))
|
||||||
|
sectionItems?.addView(card)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addCommentCard(title: String, body: String, imageUrl: String?) {
|
||||||
|
val card = createHorizontalCard()
|
||||||
|
card.addView(createImage(imageUrl, 28.dp(), 28.dp()))
|
||||||
|
card.addView(createTextGroup(title, body, bodyMaxLines = 2))
|
||||||
|
sectionItems?.addView(card)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHorizontalCard(): LinearLayout =
|
||||||
|
LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
gravity = android.view.Gravity.CENTER_VERTICAL
|
||||||
|
setPadding(14.dp(), 14.dp(), 14.dp(), 14.dp())
|
||||||
|
setBackgroundResource(R.drawable.bg_round_corner_16_7_222222)
|
||||||
|
layoutParams = defaultBlockLayoutParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTextGroup(title: String, body: String, bodyMaxLines: Int): LinearLayout {
|
||||||
|
val textGroup = LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
|
||||||
|
marginStart = 12.dp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textGroup.addView(createText(title, R.style.Typography_Body2, R.color.white, maxLines = 2))
|
||||||
|
textGroup.addView(createText(body, R.style.Typography_Caption2, R.color.gray_500, maxLines = bodyMaxLines))
|
||||||
|
return textGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createContentTile(
|
||||||
|
title: String,
|
||||||
|
body: String,
|
||||||
|
imageUrl: String?,
|
||||||
|
imageWidth: Int,
|
||||||
|
imageHeight: Int
|
||||||
|
): LinearLayout = LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
layoutParams = LinearLayout.LayoutParams(imageWidth, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
|
||||||
|
marginEnd = 12.dp()
|
||||||
|
}
|
||||||
|
addView(createImage(imageUrl, imageWidth, imageHeight))
|
||||||
|
addView(createText(title, R.style.Typography_Body2, R.color.white, maxLines = 2))
|
||||||
|
addView(createText(body, R.style.Typography_Caption2, R.color.gray_500, maxLines = 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createImage(imageUrl: String?, width: Int, height: Int): ImageView = ImageView(itemView.context).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(width, height).apply {
|
||||||
|
bottomMargin = 8.dp()
|
||||||
|
}
|
||||||
|
scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
setBackgroundResource(R.drawable.bg_round_corner_16_7_222222)
|
||||||
|
isVisible = !imageUrl.isNullOrBlank()
|
||||||
|
imageUrl?.let(::loadUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHorizontalRow(): LinearLayout = LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
layoutParams = defaultBlockLayoutParams()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHorizontalScrollRow(row: LinearLayout): HorizontalScrollView =
|
||||||
|
HorizontalScrollView(itemView.context).apply {
|
||||||
|
isHorizontalScrollBarEnabled = false
|
||||||
|
overScrollMode = View.OVER_SCROLL_NEVER
|
||||||
|
layoutParams = defaultBlockLayoutParams()
|
||||||
|
addView(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun defaultBlockLayoutParams(): LinearLayout.LayoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
bottomMargin = 8.dp()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addActivityRow(label: String, value: String) {
|
||||||
|
val row = LinearLayout(itemView.context).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
setPadding(14.dp(), 8.dp(), 14.dp(), 8.dp())
|
||||||
|
setBackgroundResource(R.drawable.bg_round_corner_16_7_222222)
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
bottomMargin = 6.dp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row.addView(
|
||||||
|
createText(label, R.style.Typography_Caption2, R.color.gray_500, maxLines = 1).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
row.addView(createText(value, R.style.Typography_Body2, R.color.white, maxLines = 1))
|
||||||
|
sectionItems?.addView(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSnsButton(iconResId: Int, url: String, isLast: Boolean): ImageView =
|
||||||
|
ImageView(itemView.context).apply {
|
||||||
|
val buttonSize = calculateCreatorChannelSnsButtonSizeDp(itemView.resources.configuration.screenWidthDp)
|
||||||
|
.dp()
|
||||||
|
setImageResource(iconResId)
|
||||||
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
|
layoutParams = LinearLayout.LayoutParams(buttonSize, buttonSize).apply {
|
||||||
|
marginEnd = if (isLast) 0 else 16.dp()
|
||||||
|
}
|
||||||
|
setOnClickListener {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||||
|
if (intent.resolveActivity(itemView.context.packageManager) != null) {
|
||||||
|
itemView.context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDebutActivityValue(debutDateUtc: String?, dDay: String): String {
|
||||||
|
val debutDate = debutDateUtc.orEmpty()
|
||||||
|
return if (debutDate.isBlank()) {
|
||||||
|
dDay
|
||||||
|
} else {
|
||||||
|
itemView.context.getString(R.string.creator_channel_activity_debut_format, debutDate, dDay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createText(text: String, styleResId: Int, colorResId: Int, maxLines: Int): TextView =
|
||||||
|
TextView(itemView.context).apply {
|
||||||
|
setTextAppearance(styleResId)
|
||||||
|
this.text = text
|
||||||
|
setTextColor(itemView.context.getColor(colorResId))
|
||||||
|
this.maxLines = maxLines
|
||||||
|
ellipsize = android.text.TextUtils.TruncateAt.END
|
||||||
|
isVisible = text.isNotBlank()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.dp(): Int = (this * itemView.resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
@get:LayoutRes
|
||||||
|
val CreatorChannelHomeSection.layoutResId: Int
|
||||||
|
get() = when (this) {
|
||||||
|
is CreatorChannelHomeSection.CurrentLive -> R.layout.item_creator_channel_home_live
|
||||||
|
is CreatorChannelHomeSection.LatestAudioContent -> R.layout.item_creator_channel_home_latest_audio
|
||||||
|
is CreatorChannelHomeSection.Donations -> R.layout.item_creator_channel_home_donation
|
||||||
|
is CreatorChannelHomeSection.Notices -> R.layout.item_creator_channel_home_notice
|
||||||
|
is CreatorChannelHomeSection.Schedules -> R.layout.item_creator_channel_home_schedule
|
||||||
|
is CreatorChannelHomeSection.AudioContents -> R.layout.item_creator_channel_home_audio
|
||||||
|
is CreatorChannelHomeSection.Series -> R.layout.item_creator_channel_home_series
|
||||||
|
is CreatorChannelHomeSection.Communities -> R.layout.item_creator_channel_home_community
|
||||||
|
is CreatorChannelHomeSection.FanTalk -> R.layout.item_creator_channel_home_fantalk
|
||||||
|
is CreatorChannelHomeSection.Introduce -> R.layout.item_creator_channel_home_introduce
|
||||||
|
is CreatorChannelHomeSection.Activity -> R.layout.item_creator_channel_home_activity
|
||||||
|
is CreatorChannelHomeSection.Sns -> R.layout.item_creator_channel_home_sns
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:StringRes
|
||||||
|
val CreatorChannelHomeSection.titleResId: Int
|
||||||
|
get() = when (this) {
|
||||||
|
is CreatorChannelHomeSection.CurrentLive -> R.string.creator_channel_section_live
|
||||||
|
is CreatorChannelHomeSection.LatestAudioContent -> R.string.creator_channel_section_latest_audio
|
||||||
|
is CreatorChannelHomeSection.Donations -> R.string.creator_channel_section_donation
|
||||||
|
is CreatorChannelHomeSection.Notices -> R.string.creator_channel_section_notice
|
||||||
|
is CreatorChannelHomeSection.Schedules -> R.string.creator_channel_section_schedule
|
||||||
|
is CreatorChannelHomeSection.AudioContents -> R.string.creator_channel_section_audio
|
||||||
|
is CreatorChannelHomeSection.Series -> R.string.creator_channel_section_series
|
||||||
|
is CreatorChannelHomeSection.Communities -> R.string.creator_channel_section_community
|
||||||
|
is CreatorChannelHomeSection.FanTalk -> R.string.creator_channel_section_fantalk
|
||||||
|
is CreatorChannelHomeSection.Introduce -> R.string.creator_channel_section_introduce
|
||||||
|
is CreatorChannelHomeSection.Activity -> R.string.creator_channel_section_activity
|
||||||
|
is CreatorChannelHomeSection.Sns -> R.string.creator_channel_section_sns
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val MAX_DONATION_ITEM_COUNT = 8
|
||||||
|
private const val MAX_NOTICE_ITEM_COUNT = 3
|
||||||
|
private const val MAX_SCHEDULE_ITEM_COUNT = 3
|
||||||
|
|
||||||
|
fun List<String>.joinToText(): String = filter(String::isNotBlank).joinToString(separator = " · ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatCreatorChannelScheduleDate(
|
||||||
|
scheduledAtUtc: String,
|
||||||
|
timeZone: TimeZone = TimeZone.getDefault(),
|
||||||
|
locale: Locale = Locale.getDefault()
|
||||||
|
): String = formatCreatorChannelScheduleUtc(scheduledAtUtc, "d", timeZone, locale)
|
||||||
|
|
||||||
|
internal fun formatCreatorChannelScheduleDayOfWeek(
|
||||||
|
scheduledAtUtc: String,
|
||||||
|
timeZone: TimeZone = TimeZone.getDefault(),
|
||||||
|
locale: Locale = Locale.getDefault()
|
||||||
|
): String = formatCreatorChannelScheduleUtc(scheduledAtUtc, "E", timeZone, locale)
|
||||||
|
|
||||||
|
internal fun formatCreatorChannelScheduleTime(
|
||||||
|
scheduledAtUtc: String,
|
||||||
|
timeZone: TimeZone = TimeZone.getDefault(),
|
||||||
|
locale: Locale = Locale.getDefault()
|
||||||
|
): String = formatCreatorChannelScheduleUtc(scheduledAtUtc, "a hh:mm", timeZone, locale)
|
||||||
|
|
||||||
|
private fun formatCreatorChannelScheduleUtc(
|
||||||
|
scheduledAtUtc: String,
|
||||||
|
pattern: String,
|
||||||
|
timeZone: TimeZone,
|
||||||
|
locale: Locale
|
||||||
|
): String {
|
||||||
|
val date = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
||||||
|
this.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
isLenient = false
|
||||||
|
}.parse(scheduledAtUtc) ?: return ""
|
||||||
|
return SimpleDateFormat(pattern, locale).apply { this.timeZone = timeZone }.format(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun calculateCreatorChannelSnsButtonSizeDp(screenWidthDp: Int): Int {
|
||||||
|
val width = screenWidthDp.takeIf { it > 0 } ?: 402
|
||||||
|
return if (width >= 402) {
|
||||||
|
52
|
||||||
|
} else {
|
||||||
|
(52f * width / 402f).roundToInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
internal fun calculateCreatorChannelDonationHeaderColorRes(can: Int): Int = when {
|
||||||
|
can >= 500 -> R.color.red_400
|
||||||
|
can >= 101 -> R.color.creator_channel_donation_cyan
|
||||||
|
can >= 51 -> R.color.green_400
|
||||||
|
else -> R.color.gray_200
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun calculateCreatorChannelDonationCardWidthDp(screenWidthDp: Int): Int {
|
||||||
|
val width = screenWidthDp.takeIf { it > 0 } ?: 402
|
||||||
|
return if (width >= 402) {
|
||||||
|
374
|
||||||
|
} else {
|
||||||
|
(374f * width / 402f).roundToInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun calculateCreatorChannelNoticeCardWidthDp(screenWidthDp: Int): Int {
|
||||||
|
val width = screenWidthDp.takeIf { it > 0 } ?: 402
|
||||||
|
return if (width >= 402) {
|
||||||
|
346
|
||||||
|
} else {
|
||||||
|
(346f * width / 402f).roundToInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun calculateCreatorChannelScheduleTimelineLineCount(scheduleCount: Int): Int = (scheduleCount - 1).coerceAtLeast(0)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingTop="@dimen/spacing_20">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_section_title"
|
||||||
|
style="@style/Typography.Heading3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="섹션" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_section_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
25
app/src/main/res/layout/item_creator_channel_home_audio.xml
Normal file
25
app/src/main/res/layout/item_creator_channel_home_audio.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingTop="@dimen/spacing_20">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_section_title"
|
||||||
|
style="@style/Typography.Heading3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="섹션" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_section_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingTop="@dimen/spacing_20">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_section_title"
|
||||||
|
style="@style/Typography.Heading3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="섹션" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_section_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingTop="@dimen/spacing_20">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_section_title"
|
||||||
|
style="@style/Typography.Heading3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="섹션" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_section_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingTop="@dimen/spacing_20">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_section_title"
|
||||||
|
style="@style/Typography.Heading3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="섹션" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_section_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
25
app/src/main/res/layout/item_creator_channel_home_series.xml
Normal file
25
app/src/main/res/layout/item_creator_channel_home_series.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingTop="@dimen/spacing_20">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_section_title"
|
||||||
|
style="@style/Typography.Heading3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="섹션" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_section_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
25
app/src/main/res/layout/item_creator_channel_home_sns.xml
Normal file
25
app/src/main/res/layout/item_creator_channel_home_sns.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
|
android:paddingTop="@dimen/spacing_20">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_section_title"
|
||||||
|
style="@style/Typography.Heading3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
tools:text="섹션" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_section_items"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/spacing_8"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user