feat(creator): 채널 홈 섹션 렌더링을 연결한다

This commit is contained in:
2026-06-15 13:21:15 +09:00
parent 2253dabfe9
commit 11fd892310
8 changed files with 791 additions and 0 deletions

View File

@@ -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)

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>