feat(creator): 채널 후원 버튼을 연결한다

This commit is contained in:
2026-06-16 19:22:28 +09:00
parent de351d700c
commit 28433c10df
4 changed files with 149 additions and 11 deletions

View File

@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.MeasureSpec import android.view.View.MeasureSpec
import android.widget.LinearLayout import android.widget.LinearLayout
@@ -26,6 +27,7 @@ import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.extensions.loadUrl
import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog
import kr.co.vividnext.sodalive.report.ProfileReportDialog import kr.co.vividnext.sodalive.report.ProfileReportDialog
import kr.co.vividnext.sodalive.report.UserReportDialog import kr.co.vividnext.sodalive.report.UserReportDialog
import kr.co.vividnext.sodalive.v2.common.CreatorActivityType import kr.co.vividnext.sodalive.v2.common.CreatorActivityType
@@ -302,6 +304,23 @@ class CreatorChannelActivity :
updateViewPagerHeight() updateViewPagerHeight()
} }
override fun onCreatorChannelDonationClicked() {
val header = currentHeader ?: return
if (header.isOwner) return
val dialog = LiveRoomDonationDialog(
this,
LayoutInflater.from(this),
isLiveDonation = true,
messageMaxLength = 100,
secretToggleLabelResId = R.string.screen_user_profile_channel_donation_secret,
applySecretMissionMessageHint = false
) { can, message, isSecret ->
homeActionDelegate?.postChannelDonation(can = can, isSecret = isSecret, message = message)
}
dialog.show(screenWidth - 26.7f.dpToPx().toInt())
}
private fun updateViewPagerHeight() { private fun updateViewPagerHeight() {
binding.viewPager.post { binding.viewPager.post {
val recyclerView = binding.viewPager.getChildAt(0) as? RecyclerView ?: return@post val recyclerView = binding.viewPager.getChildAt(0) as? RecyclerView ?: return@post

View File

@@ -18,7 +18,11 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
) { ) {
private val viewModel: CreatorChannelHomeViewModel by viewModel() private val viewModel: CreatorChannelHomeViewModel by viewModel()
private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked) private val sectionAdapter = CreatorChannelHomeSectionAdapter(
onScheduleClick = ::onScheduleClicked,
onAudioContentClick = ::onAudioContentClicked,
onDonationClick = ::onDonationClicked
)
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L } private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
private val host: Host private val host: Host
get() = requireActivity() as Host get() = requireActivity() as Host
@@ -49,6 +53,10 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
override fun reportProfile() { override fun reportProfile() {
viewModel.reportProfile() viewModel.reportProfile()
} }
override fun postChannelDonation(can: Int, isSecret: Boolean, message: String) {
viewModel.postChannelDonation(can = can, isSecret = isSecret, message = message)
}
} }
) )
if (creatorId > 0L) { if (creatorId > 0L) {
@@ -97,6 +105,10 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
host.onCreatorChannelAudioContentClicked(audioContent) host.onCreatorChannelAudioContentClicked(audioContent)
} }
private fun onDonationClicked() {
host.onCreatorChannelDonationClicked()
}
interface Host { interface Host {
fun onCreatorChannelHeaderChanged(header: CreatorChannelHeaderUiModel) fun onCreatorChannelHeaderChanged(header: CreatorChannelHeaderUiModel)
fun onCreatorChannelFollowProgressChanged(inProgress: Boolean) fun onCreatorChannelFollowProgressChanged(inProgress: Boolean)
@@ -105,6 +117,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
fun onCreatorChannelAudioContentClicked(audioContent: CreatorChannelAudioContentResponse) fun onCreatorChannelAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)
fun onCreatorChannelHomeActionDelegateReady(delegate: HomeActionDelegate?) fun onCreatorChannelHomeActionDelegateReady(delegate: HomeActionDelegate?)
fun onCreatorChannelHomeContentChanged() fun onCreatorChannelHomeContentChanged()
fun onCreatorChannelDonationClicked()
} }
interface HomeActionDelegate { interface HomeActionDelegate {
@@ -113,6 +126,7 @@ class CreatorChannelHomeFragment : BaseFragment<FragmentCreatorChannelHomeBindin
fun blockUser() fun blockUser()
fun reportUser(reason: String) fun reportUser(reason: String)
fun reportProfile() fun reportProfile()
fun postChannelDonation(can: Int, isSecret: Boolean, message: String)
} }
companion object { companion object {

View File

@@ -17,6 +17,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.common.formatUtcRelativeTimeText
import kr.co.vividnext.sodalive.common.image.BlurTransformation import kr.co.vividnext.sodalive.common.image.BlurTransformation
import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.extensions.loadUrl
import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.extensions.moneyFormat
@@ -36,7 +37,8 @@ import kotlin.math.roundToInt
class CreatorChannelHomeSectionAdapter( class CreatorChannelHomeSectionAdapter(
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit = {}, private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit = {},
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit = {}, private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit = {},
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit = {} private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit = {},
private val onDonationClick: () -> Unit = {}
) : RecyclerView.Adapter<CreatorChannelHomeSectionAdapter.SectionViewHolder>() { ) : RecyclerView.Adapter<CreatorChannelHomeSectionAdapter.SectionViewHolder>() {
private var items: List<CreatorChannelHomeSection> = emptyList() private var items: List<CreatorChannelHomeSection> = emptyList()
@@ -50,7 +52,7 @@ class CreatorChannelHomeSectionAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return SectionViewHolder(view, onScheduleClick, onAudioContentClick, onSeriesClick) return SectionViewHolder(view, onScheduleClick, onAudioContentClick, onSeriesClick, onDonationClick)
} }
override fun onBindViewHolder(holder: SectionViewHolder, position: Int) { override fun onBindViewHolder(holder: SectionViewHolder, position: Int) {
@@ -63,7 +65,8 @@ class CreatorChannelHomeSectionAdapter(
view: View, view: View,
private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit, private val onScheduleClick: (CreatorChannelScheduleResponse) -> Unit,
private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit, private val onAudioContentClick: (CreatorChannelAudioContentResponse) -> Unit,
private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit private val onSeriesClick: (CreatorChannelSeriesResponse) -> Unit,
private val onDonationClick: () -> Unit
) : RecyclerView.ViewHolder(view) { ) : RecyclerView.ViewHolder(view) {
private val title: TextView? = view.findViewById(R.id.tv_section_title) private val title: TextView? = view.findViewById(R.id.tv_section_title)
private val currentLiveTitle: TextView? = view.findViewById(R.id.tv_current_live_title) private val currentLiveTitle: TextView? = view.findViewById(R.id.tv_current_live_title)
@@ -75,7 +78,12 @@ class CreatorChannelHomeSectionAdapter(
private val latestAudioPointTag: ImageView? = view.findViewById(R.id.iv_latest_audio_point_tag) 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 latestAudioTitle: TextView? = view.findViewById(R.id.tv_latest_audio_title)
private val latestAudioDuration: TextView? = view.findViewById(R.id.tv_latest_audio_duration) private val latestAudioDuration: TextView? = view.findViewById(R.id.tv_latest_audio_duration)
private val donationItemsScrollView: View? = view.findViewById(R.id.hsv_donation_items)
private val donationItems: LinearLayout? = view.findViewById(R.id.ll_donation_items) private val donationItems: LinearLayout? = view.findViewById(R.id.ll_donation_items)
private val donationEmpty: View? = view.findViewById(R.id.layout_donation_empty)
private val donationEmptyButton: View? = view.findViewById(R.id.layout_donation_empty_button)
private val donationButton: View? = view.findViewById(R.id.layout_donation_button)
private val donationEmptyTitle: TextView? = view.findViewById(R.id.tv_donation_empty_title)
private val noticeItems: LinearLayout? = view.findViewById(R.id.ll_notice_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 scheduleTimeline: LinearLayout? = view.findViewById(R.id.ll_schedule_timeline)
private val scheduleItems: LinearLayout? = view.findViewById(R.id.ll_schedule_items) private val scheduleItems: LinearLayout? = view.findViewById(R.id.ll_schedule_items)
@@ -107,6 +115,7 @@ class CreatorChannelHomeSectionAdapter(
} }
fun bind(item: CreatorChannelHomeSection) { fun bind(item: CreatorChannelHomeSection) {
itemView.setOnClickListener(null)
title?.setText(item.titleResId) title?.setText(item.titleResId)
donationItems?.removeAllViews() donationItems?.removeAllViews()
noticeItems?.removeAllViews() noticeItems?.removeAllViews()
@@ -144,9 +153,26 @@ class CreatorChannelHomeSectionAdapter(
latestAudioDuration?.text = item.audioContent.duration.orEmpty() latestAudioDuration?.text = item.audioContent.duration.orEmpty()
latestAudioPointTag?.isVisible = item.audioContent.isPointAvailable latestAudioPointTag?.isVisible = item.audioContent.isPointAvailable
latestAudioThumbnail?.loadUrl(item.audioContent.imageUrl) latestAudioThumbnail?.loadUrl(item.audioContent.imageUrl)
itemView.setOnClickListener { onAudioContentClick(item.audioContent) }
} }
private fun bindDonations(item: CreatorChannelHomeSection.Donations) { private fun bindDonations(item: CreatorChannelHomeSection.Donations) {
donationItems?.removeAllViews()
donationItemsScrollView?.isVisible = item.donations.isNotEmpty()
donationEmpty?.isVisible = item.donations.isEmpty()
val isDonationButtonVisible = item.donations.isNotEmpty() && !item.isOwner
val isDonationEmptyButtonVisible = !item.isOwner
donationButton?.isVisible = isDonationButtonVisible
donationEmptyButton?.isVisible = isDonationEmptyButtonVisible
donationEmptyTitle?.setText(
if (item.isOwner) {
R.string.creator_channel_donation_empty_owner_title
} else {
R.string.creator_channel_donation_empty_title
}
)
donationButton?.setOnClickListener(if (isDonationButtonVisible) View.OnClickListener { onDonationClick() } else null)
donationEmptyButton?.setOnClickListener(if (isDonationEmptyButtonVisible) View.OnClickListener { onDonationClick() } else null)
val visibleDonations = item.donations.take(MAX_DONATION_ITEM_COUNT) val visibleDonations = item.donations.take(MAX_DONATION_ITEM_COUNT)
visibleDonations.forEachIndexed { index, donation -> visibleDonations.forEachIndexed { index, donation ->
val row = LayoutInflater.from(itemView.context).inflate( val row = LayoutInflater.from(itemView.context).inflate(
@@ -167,7 +193,8 @@ class CreatorChannelHomeSectionAdapter(
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
row.findViewById<TextView>(R.id.tv_donation_nickname).text = donation.nickname 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_created_at).text =
formatUtcRelativeTimeText(itemView.context, donation.createdAtUtc)
row.findViewById<TextView>(R.id.tv_donation_can).text = itemView.context.getString( row.findViewById<TextView>(R.id.tv_donation_can).text = itemView.context.getString(
R.string.creator_channel_donation_can_format, R.string.creator_channel_donation_can_format,
donation.can.moneyFormat() donation.can.moneyFormat()
@@ -198,7 +225,8 @@ class CreatorChannelHomeSectionAdapter(
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
row.findViewById<TextView>(R.id.tv_notice_creator_name).text = notice.creatorNickname 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_created_at).text =
formatUtcRelativeTimeText(itemView.context, notice.dateUtc)
row.findViewById<TextView>(R.id.tv_notice_content).text = notice.content row.findViewById<TextView>(R.id.tv_notice_content).text = notice.content
val noticeThumbnail = row.findViewById<ImageView>(R.id.iv_notice_thumbnail) val noticeThumbnail = row.findViewById<ImageView>(R.id.iv_notice_thumbnail)
noticeThumbnail.isVisible = !notice.imageUrl.isNullOrBlank() noticeThumbnail.isVisible = !notice.imageUrl.isNullOrBlank()
@@ -383,7 +411,7 @@ class CreatorChannelHomeSectionAdapter(
postId = postId.toString(), postId = postId.toString(),
bodyText = content, bodyText = content,
keywordText = "", keywordText = "",
createdAtText = dateUtc, createdAtText = formatUtcRelativeTimeText(itemView.context, dateUtc),
commentCount = commentCount, commentCount = commentCount,
likeCount = likeCount, likeCount = likeCount,
imageUrl = imageUrl, imageUrl = imageUrl,

View File

@@ -207,7 +207,7 @@ class CreatorChannelActivitySourceTest {
assertTrue(source.contains("arguments = Bundle().apply")) assertTrue(source.contains("arguments = Bundle().apply"))
assertTrue(source.contains("putLong(ARG_CREATOR_ID, creatorId)")) assertTrue(source.contains("putLong(ARG_CREATOR_ID, creatorId)"))
assertTrue(source.contains("private val viewModel: CreatorChannelHomeViewModel by viewModel()")) assertTrue(source.contains("private val viewModel: CreatorChannelHomeViewModel by viewModel()"))
assertTrue(source.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)")) assertTrue(source.contains("onDonationClick = ::onDonationClicked"))
assertTrue(source.contains("binding.rvHomeSections.layoutManager = LinearLayoutManager(requireContext())")) assertTrue(source.contains("binding.rvHomeSections.layoutManager = LinearLayoutManager(requireContext())"))
assertTrue(source.contains("binding.rvHomeSections.adapter = sectionAdapter")) assertTrue(source.contains("binding.rvHomeSections.adapter = sectionAdapter"))
assertTrue(source.contains("viewModel.homeStateLiveData.observe(viewLifecycleOwner)")) assertTrue(source.contains("viewModel.homeStateLiveData.observe(viewLifecycleOwner)"))
@@ -610,6 +610,8 @@ class CreatorChannelActivitySourceTest {
assertTrue(adapter.contains("latestAudioTitle?.text = item.audioContent.title")) assertTrue(adapter.contains("latestAudioTitle?.text = item.audioContent.title"))
assertTrue(adapter.contains("latestAudioThumbnail?.loadUrl")) assertTrue(adapter.contains("latestAudioThumbnail?.loadUrl"))
assertTrue(adapter.contains("latestAudioPointTag?.isVisible = item.audioContent.isPointAvailable")) assertTrue(adapter.contains("latestAudioPointTag?.isVisible = item.audioContent.isPointAvailable"))
assertTrue(adapter.contains("itemView.setOnClickListener(null)"))
assertTrue(adapter.contains("itemView.setOnClickListener { onAudioContentClick(item.audioContent) }"))
assertFalse(adapter.contains("private fun addAudioCard")) assertFalse(adapter.contains("private fun addAudioCard"))
assertFalse(adapter.contains("addAudioCard(item.audioContent)")) assertFalse(adapter.contains("addAudioCard(item.audioContent)"))
} }
@@ -625,9 +627,15 @@ class CreatorChannelActivitySourceTest {
assertTrue(donationLayout.contains("@layout/view_section_title")) assertTrue(donationLayout.contains("@layout/view_section_title"))
assertTrue(donationLayout.contains("@+id/hsv_donation_items")) assertTrue(donationLayout.contains("@+id/hsv_donation_items"))
assertTrue(donationLayout.contains("@+id/ll_donation_items")) assertTrue(donationLayout.contains("@+id/ll_donation_items"))
assertTrue(donationLayout.contains("@+id/layout_donation_empty"))
assertTrue(donationLayout.contains("@+id/tv_donation_empty_title"))
assertTrue(donationLayout.contains("@string/creator_channel_donation_empty_title"))
assertTrue(donationLayout.contains("android:layout_width=\"match_parent\""))
assertTrue(donationLayout.contains("android:layout_height=\"196dp\""))
assertTrue(donationLayout.contains("@+id/layout_donation_button")) assertTrue(donationLayout.contains("@+id/layout_donation_button"))
assertTrue(donationLayout.contains("@drawable/ic_new_donation")) assertTrue(donationLayout.contains("@drawable/ic_new_donation"))
assertFalse(donationLayout.contains("@+id/tv_donation_button")) assertFalse(donationLayout.contains("@+id/tv_donation_button"))
assertFalse(donationLayout.contains("android:layout_width=\"374dp\""))
assertFalse(donationLayout.contains("@+id/ll_section_items")) assertFalse(donationLayout.contains("@+id/ll_section_items"))
val donationCardView = projectFile( val donationCardView = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelDonationCardView.kt" "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelDonationCardView.kt"
@@ -644,16 +652,63 @@ class CreatorChannelActivitySourceTest {
assertTrue(donationRowLayout.contains("@drawable/ic_can")) assertTrue(donationRowLayout.contains("@drawable/ic_can"))
assertFalse(donationRowLayout.contains("android:layout_width=\"374dp\"")) assertFalse(donationRowLayout.contains("android:layout_width=\"374dp\""))
assertTrue(adapter.contains("private val donationItems: LinearLayout?")) assertTrue(adapter.contains("private val donationItems: LinearLayout?"))
assertTrue(adapter.contains("private val donationItemsScrollView: View?"))
assertTrue(adapter.contains("private val donationEmpty: View?"))
assertTrue(adapter.contains("donationItemsScrollView?.isVisible = item.donations.isNotEmpty()"))
assertTrue(adapter.contains("donationEmpty?.isVisible = item.donations.isEmpty()"))
assertTrue(adapter.contains("private fun bindDonations(item: CreatorChannelHomeSection.Donations) {\n donationItems?.removeAllViews()"))
assertTrue(adapter.contains("R.layout.item_creator_channel_home_donation_row")) assertTrue(adapter.contains("R.layout.item_creator_channel_home_donation_row"))
assertTrue(adapter.contains("donationItems?.addView(row)")) assertTrue(adapter.contains("donationItems?.addView(row)"))
assertTrue(adapter.contains("val visibleDonations = item.donations.take(MAX_DONATION_ITEM_COUNT)")) assertTrue(adapter.contains("val visibleDonations = item.donations.take(MAX_DONATION_ITEM_COUNT)"))
assertTrue(adapter.contains("calculateCreatorChannelDonationCardWidthDp")) assertTrue(adapter.contains("calculateCreatorChannelDonationCardWidthDp"))
assertTrue(adapter.contains("calculateCreatorChannelDonationHeaderColorRes(donation.can)")) assertTrue(adapter.contains("calculateCreatorChannelDonationHeaderColorRes(donation.can)"))
assertTrue(adapter.contains("formatUtcRelativeTimeText(itemView.context, donation.createdAtUtc)"))
assertFalse(adapter.contains("tv_donation_created_at).text = donation.createdAtUtc"))
assertTrue(adapter.contains("R.string.creator_channel_donation_fallback_message")) assertTrue(adapter.contains("R.string.creator_channel_donation_fallback_message"))
assertFalse(adapter.contains("private fun addDonationCard")) assertFalse(adapter.contains("private fun addDonationCard"))
assertFalse(adapter.contains("addDonationCard(")) assertFalse(adapter.contains("addDonationCard("))
} }
@Test
fun `후원 버튼은 본인 채널에서 숨기고 타인 채널에서 다이얼로그를 연다`() {
val adapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt"
).readText()
val fragment = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt"
).readText()
val activity = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
).readText()
assertTrue(adapter.contains("private val donationEmptyButton: View?"))
assertTrue(adapter.contains("private val onDonationClick: () -> Unit"))
assertTrue(adapter.contains("val isDonationButtonVisible = item.donations.isNotEmpty() && !item.isOwner"))
assertTrue(adapter.contains("val isDonationEmptyButtonVisible = !item.isOwner"))
assertTrue(adapter.contains("donationButton?.isVisible = isDonationButtonVisible"))
assertTrue(adapter.contains("donationEmptyButton?.isVisible = isDonationEmptyButtonVisible"))
assertTrue(
adapter.contains(
"donationButton?.setOnClickListener(if (isDonationButtonVisible) View.OnClickListener { onDonationClick() } else null)"
)
)
assertTrue(
adapter.contains(
"donationEmptyButton?.setOnClickListener(if (isDonationEmptyButtonVisible) View.OnClickListener { onDonationClick() } else null)"
)
)
assertTrue(fragment.contains("override fun postChannelDonation(can: Int, isSecret: Boolean, message: String)"))
assertTrue(fragment.contains("viewModel.postChannelDonation(can = can, isSecret = isSecret, message = message)"))
assertTrue(fragment.contains("host.onCreatorChannelDonationClicked()"))
assertTrue(activity.contains("LiveRoomDonationDialog"))
assertTrue(activity.contains("isLiveDonation = true"))
assertTrue(activity.contains("messageMaxLength = 100"))
assertTrue(activity.contains("secretToggleLabelResId = R.string.screen_user_profile_channel_donation_secret"))
assertTrue(activity.contains("applySecretMissionMessageHint = false"))
assertTrue(activity.contains("homeActionDelegate?.postChannelDonation(can = can, isSecret = isSecret, message = message)"))
assertTrue(activity.contains("dialog.show(screenWidth - 26.7f.dpToPx().toInt())"))
}
@Test @Test
fun `후원 can 수량은 요청된 배경색 resource 경계로 매핑한다`() { fun `후원 can 수량은 요청된 배경색 resource 경계로 매핑한다`() {
assertEquals(R.color.gray_200, calculateCreatorChannelDonationHeaderColorRes(1)) assertEquals(R.color.gray_200, calculateCreatorChannelDonationHeaderColorRes(1))
@@ -684,6 +739,17 @@ class CreatorChannelActivitySourceTest {
assertTrue(ja.contains("creator_channel_donation_fallback_message")) assertTrue(ja.contains("creator_channel_donation_fallback_message"))
} }
@Test
fun `후원 empty 안내 문구는 다국어 string resource로 제공한다`() {
val ko = projectFile("app/src/main/res/values/strings.xml").readText()
val en = projectFile("app/src/main/res/values-en/strings.xml").readText()
val ja = projectFile("app/src/main/res/values-ja/strings.xml").readText()
assertTrue(ko.contains("name=\"creator_channel_donation_empty_title\">처음으로 크리에이터를\\n후원해 보세요!"))
assertTrue(en.contains("creator_channel_donation_empty_title"))
assertTrue(ja.contains("creator_channel_donation_empty_title"))
}
@Test @Test
fun `공지 섹션은 최대 3개 Figma feed card를 가로 row로 렌더링한다`() { fun `공지 섹션은 최대 3개 Figma feed card를 가로 row로 렌더링한다`() {
val adapter = projectFile( val adapter = projectFile(
@@ -727,7 +793,8 @@ class CreatorChannelActivitySourceTest {
assertTrue(adapter.contains("noticeItems?.addView(row)")) assertTrue(adapter.contains("noticeItems?.addView(row)"))
assertTrue(adapter.contains("row.findViewById<ImageView>(R.id.iv_notice_profile).loadUrl")) assertTrue(adapter.contains("row.findViewById<ImageView>(R.id.iv_notice_profile).loadUrl"))
assertTrue(adapter.contains("row.findViewById<TextView>(R.id.tv_notice_creator_name).text = notice.creatorNickname")) assertTrue(adapter.contains("row.findViewById<TextView>(R.id.tv_notice_creator_name).text = notice.creatorNickname"))
assertTrue(adapter.contains("row.findViewById<TextView>(R.id.tv_notice_created_at).text = notice.dateUtc")) assertTrue(adapter.contains("row.findViewById<TextView>(R.id.tv_notice_created_at).text ="))
assertTrue(adapter.contains("formatUtcRelativeTimeText(itemView.context, notice.dateUtc)"))
assertTrue(adapter.contains("row.findViewById<TextView>(R.id.tv_notice_content).text = notice.content")) assertTrue(adapter.contains("row.findViewById<TextView>(R.id.tv_notice_content).text = notice.content"))
assertTrue(adapter.contains("noticeThumbnail.isVisible = !notice.imageUrl.isNullOrBlank()")) assertTrue(adapter.contains("noticeThumbnail.isVisible = !notice.imageUrl.isNullOrBlank()"))
assertTrue(adapter.contains("marginEnd = if (index == visibleNotices.lastIndex) 0 else 4.dp()")) assertTrue(adapter.contains("marginEnd = if (index == visibleNotices.lastIndex) 0 else 4.dp()"))
@@ -759,6 +826,16 @@ class CreatorChannelActivitySourceTest {
assertFalse(adapter.contains("schedule.type.code")) assertFalse(adapter.contains("schedule.type.code"))
} }
@Test
fun `커뮤니티 날짜는 dateUtc 원문 대신 상대 시간 formatter를 사용한다`() {
val adapter = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt"
).readText()
assertTrue(adapter.contains("formatUtcRelativeTimeText(itemView.context, dateUtc)"))
assertFalse(adapter.contains("createdAtText = dateUtc,"))
}
@Test @Test
fun `일정 섹션은 Figma 전용 layout과 row bind로 최대 3개를 렌더링한다`() { fun `일정 섹션은 Figma 전용 layout과 row bind로 최대 3개를 렌더링한다`() {
val adapter = projectFile( val adapter = projectFile(
@@ -828,7 +905,7 @@ class CreatorChannelActivitySourceTest {
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt" "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt"
).readText() ).readText()
assertTrue(fragment.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked")) assertTrue(fragment.contains("onScheduleClick = ::onScheduleClicked"))
assertTrue(fragment.contains("private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse)")) assertTrue(fragment.contains("private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse)"))
assertTrue(fragment.contains("host.onCreatorChannelScheduleClicked(schedule)")) assertTrue(fragment.contains("host.onCreatorChannelScheduleClicked(schedule)"))
assertTrue(source.contains("private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse)")) assertTrue(source.contains("private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse)"))
@@ -907,7 +984,7 @@ class CreatorChannelActivitySourceTest {
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt" "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt"
).readText() ).readText()
assertTrue(fragment.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)")) assertTrue(fragment.contains("onAudioContentClick = ::onAudioContentClicked"))
assertTrue(fragment.contains("private fun onAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)")) assertTrue(fragment.contains("private fun onAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)"))
assertTrue(fragment.contains("host.onCreatorChannelAudioContentClicked(audioContent)")) assertTrue(fragment.contains("host.onCreatorChannelAudioContentClicked(audioContent)"))
assertTrue(source.contains("private fun onAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)")) assertTrue(source.contains("private fun onAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)"))