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