From 81537b1e9619be20397193801e27ae07df5cfa8d Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 9 Jul 2026 00:54:06 +0900 Subject: [PATCH] =?UTF-8?q?feat(creator-channel):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EB=AA=A9=EB=A1=9D=20=ED=81=B4=EB=A6=AD?= =?UTF-8?q?=EC=9D=84=20=EC=97=B0=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/CreatorChannelCommunityGridAdapter.kt | 13 +++++++-- .../ui/CreatorChannelCommunityListAdapter.kt | 4 +++ .../CreatorChannelActivitySourceTest.kt | 29 +++++++++++++++++++ ...eatorChannelCommunityFragmentLayoutTest.kt | 21 ++++++++++++-- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt index 148eae0a..9768a9a7 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt @@ -13,7 +13,9 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityImageMode import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel -class CreatorChannelCommunityGridAdapter : RecyclerView.Adapter() { +class CreatorChannelCommunityGridAdapter( + private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {} +) : RecyclerView.Adapter() { private var items: List = emptyList() private var itemSizePx: Int = 0 @@ -31,7 +33,10 @@ class CreatorChannelCommunityGridAdapter : RecyclerView.Adapter Unit ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: CreatorChannelCommunityPostUiModel, itemSizePx: Int) = with(binding) { + root.setOnClickListener { onPostClick(item) } if (itemSizePx > 0) { root.layoutParams = root.layoutParams.apply { width = itemSizePx diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt index bd327525..48abc5fe 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt @@ -17,6 +17,7 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.v2.creator.channel.community.model.CreatorChannelCommunityPostUiModel class CreatorChannelCommunityListAdapter( + private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}, private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}, private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}, private val isPlayingContent: (Long) -> Boolean = { false } @@ -32,6 +33,7 @@ class CreatorChannelCommunityListAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder( ItemCreatorChannelCommunityListBinding.inflate(LayoutInflater.from(parent.context), parent, false), + onPostClick, onPlayClick, onOwnerMoreClick, isPlayingContent @@ -46,12 +48,14 @@ class CreatorChannelCommunityListAdapter( class ViewHolder( private val binding: ItemCreatorChannelCommunityListBinding, + private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit, private val onPlayClick: (CreatorChannelCommunityPostUiModel) -> Unit, private val onOwnerMoreClick: (CreatorChannelCommunityPostUiModel) -> Unit, private val isPlayingContent: (Long) -> Boolean ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: CreatorChannelCommunityPostUiModel) = with(binding) { + root.setOnClickListener { onPostClick(item) } ivCreatorChannelCommunityListProfile.loadUrl(item.creatorProfileUrl) { crossfade(true) placeholder(R.drawable.bg_placeholder) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt index 29e35174..e740f272 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt @@ -47,6 +47,35 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("marginStart = if (isChatVisible && isDmVisible)")) } + @Test + fun `커뮤니티 게시물 클릭 source는 상세 Activity newIntent로 진입하고 invalid postId를 무시한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" + ).readText() + + assertTrue( + source.contains( + "import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.CreatorChannelCommunityDetailActivity" + ) + ) + assertTrue(source.contains("override fun onCreatorChannelCommunityPostClicked(postId: Long)")) + assertTrue(source.contains("if (postId <= 0L) return")) + assertTrue(source.contains("if (SharedPreferenceManager.token.isBlank())")) + assertTrue(source.contains("showLoginActivity()")) + assertTrue(source.contains("CreatorChannelCommunityDetailActivity.newIntent(this, postId)")) + assertTrue(source.contains("private val communityDetailLauncher = registerForActivityResult")) + assertTrue( + source.contains( + "communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))" + ) + ) + assertTrue( + source.indexOf("if (SharedPreferenceManager.token.isBlank())") < + source.indexOf("communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))") + ) + assertTrue(source.contains("refreshCreatorChannelCommunity()")) + } + @Test fun `follow notify source는 Phase 11 직접 팔로우 알림 액션을 연결한다`() { val source = projectFile( diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt index fed21d99..b8054c85 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt @@ -162,7 +162,7 @@ class CreatorChannelCommunityFragmentLayoutTest { } @Test - fun `커뮤니티 adapters source는 binding 상태 표시와 root click navigation 배제를 보장한다`() { + fun `커뮤니티 adapters source는 binding 상태 표시와 root click callback 계약을 보장한다`() { val listAdapter = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt" ).readText() @@ -182,7 +182,13 @@ class CreatorChannelCommunityFragmentLayoutTest { assertTrue(listAdapter.contains("isPlayingContent")) assertTrue(listAdapter.contains("R.drawable.ic_player_pause")) assertTrue(listAdapter.contains("R.drawable.ic_new_player_play")) + assertTrue(listAdapter.contains("private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}")) + assertTrue(listAdapter.contains("root.setOnClickListener { onPostClick(item) }")) + assertTrue(listAdapter.contains("ivCreatorChannelCommunityListPlay.setOnClickListener { onPlayClick(item) }")) assertTrue(listAdapter.contains("onOwnerMoreClick(item)")) + assertTrue(listAdapter.contains("ivCreatorChannelCommunityListOwnerMore.setOnClickListener { onOwnerMoreClick(item) }")) + assertTrue(gridAdapter.contains("private val onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit = {}")) + assertTrue(gridAdapter.contains("root.setOnClickListener { onPostClick(item) }")) assertTrue(listAdapter.contains("tvCreatorChannelCommunityListLockedPrice.isVisible = item.isLocked")) assertTrue(listAdapter.contains("layoutCreatorChannelCommunityListTopPrice.isVisible = item.showOwnerTopPrice")) assertTrue(!listAdapter.contains("item.isLocked || item.showOwnerTopPrice")) @@ -192,8 +198,17 @@ class CreatorChannelCommunityFragmentLayoutTest { assertTrue(!gridAdapter.contains("14f.dpToPx()")) assertTrue(!gridAdapter.contains("root.clipToOutline = true")) assertTrue(!listAdapter.contains(".asBitmap()")) - assertTrue(!listAdapter.contains("root.setOnClickListener")) - assertTrue(!gridAdapter.contains("root.setOnClickListener")) + } + + @Test + fun `커뮤니티 fragment source는 post click을 list grid adapter와 Host로 위임한다`() { + val fragment = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt" + ).readText() + + assertTrue(fragment.contains("onPostClick = { item -> host.onCreatorChannelCommunityPostClicked(item.postId) }")) + assertTrue(fragment.contains("private val gridAdapter = CreatorChannelCommunityGridAdapter(")) + assertTrue(fragment.contains("fun onCreatorChannelCommunityPostClicked(postId: Long)")) } @Test