From 6973816b8cf4a4c031c2700aa8a308f561695dc2 Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 14 Jul 2026 20:45:23 +0900 Subject: [PATCH] =?UTF-8?q?refactor(creator):=20=ED=81=AC=EB=A6=AC?= =?UTF-8?q?=EC=97=90=EC=9D=B4=ED=84=B0=20=EC=A0=91=EA=B7=BC=20=EA=B0=80?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EA=B3=B5=ED=86=B5=ED=99=94=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creator/channel/CreatorChannelActivity.kt | 105 ++------------ .../CreatorChannelActivitySourceTest.kt | 136 ++++++++++++++---- 2 files changed, 120 insertions(+), 121 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt index 75addc7d..cb8839ca 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt @@ -46,14 +46,9 @@ import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.live.LiveViewModel import kr.co.vividnext.sodalive.live.room.create.LiveRoomCreateActivity import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog -import kr.co.vividnext.sodalive.mypage.MyPageViewModel -import kr.co.vividnext.sodalive.mypage.auth.Auth -import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest -import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse import kr.co.vividnext.sodalive.report.UserReportDialog -import kr.co.vividnext.sodalive.settings.ContentSettingsActivity -import kr.co.vividnext.sodalive.splash.SplashActivity -import kr.co.vividnext.sodalive.user.login.LoginActivity +import kr.co.vividnext.sodalive.v2.access.AccessRequirement +import kr.co.vividnext.sodalive.v2.access.ensureV2Access import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog import kr.co.vividnext.sodalive.v2.common.CreatorActivityType import kr.co.vividnext.sodalive.v2.creator.channel.audio.CreatorChannelAudioFragment @@ -93,7 +88,6 @@ class CreatorChannelActivity : CreatorChannelDonationFragment.Host { private val liveViewModel: LiveViewModel by inject() - private val myPageViewModel: MyPageViewModel by inject() private val creatorCommunityRepository: CreatorCommunityRepository by inject() private var creatorId: Long = 0L private var currentHeader: CreatorChannelHeaderUiModel? = null @@ -180,8 +174,7 @@ class CreatorChannelActivity : return } - if (SharedPreferenceManager.token.isBlank()) { - showLoginActivity() + if (!ensureV2Access(AccessRequirement.Login)) { finish() return } @@ -227,7 +220,7 @@ class CreatorChannelActivity : binding.btnCreatorChannelFantalkFixedPlus.setOnClickListener { openFanTalkWrite() } binding.btnCreatorChannelOwnerCta.setOnClickListener { onOwnerCtaClicked() } binding.tvChatButton.setOnClickListener { - ensureLoginAndAdultAuth(isAdult = true) { + ensureV2Access(AccessRequirement.AdultContent) { currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) } } } @@ -602,12 +595,9 @@ class CreatorChannelActivity : override fun onCreatorChannelCommunityPostClicked(postId: Long) { if (postId <= 0L) return - if (SharedPreferenceManager.token.isBlank()) { - showLoginActivity() - return + ensureV2Access(AccessRequirement.Login) { + communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId)) } - - communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId)) } override fun onCreatorChannelFanTalkContentChanged() { @@ -623,14 +613,11 @@ class CreatorChannelActivity : override fun onCreatorChannelFanTalkItemClicked(item: CreatorChannelFanTalkUiModel) { if (currentHeader?.isOwner != true) return if (creatorId <= 0L || item.fanTalkId <= 0L) return - if (SharedPreferenceManager.token.isBlank()) { - showLoginActivity() - return + ensureV2Access(AccessRequirement.Login) { + fanTalkDetailLauncher.launch( + CreatorChannelFanTalkDetailActivity.newIntent(this, creatorId, item.toFanTalkDetailPayload()) + ) } - - fanTalkDetailLauncher.launch( - CreatorChannelFanTalkDetailActivity.newIntent(this, creatorId, item.toFanTalkDetailPayload()) - ) } override fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean) { @@ -917,11 +904,9 @@ class CreatorChannelActivity : private fun openFanTalkWrite() { if (creatorId <= 0L) return - if (SharedPreferenceManager.token.isBlank()) { - showLoginActivity() - return + ensureV2Access(AccessRequirement.Login) { + fanTalkWriteLauncher.launch(CreatorChannelFanTalkWriteActivity.newIntent(this, creatorId)) } - fanTalkWriteLauncher.launch(CreatorChannelFanTalkWriteActivity.newIntent(this, creatorId)) } private fun onOwnerFabCommunityClicked() { @@ -974,7 +959,7 @@ class CreatorChannelActivity : } override fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse) { - ensureLoginAndAdultAuth(isAdult = live.isAdult) { + ensureV2Access(AccessRequirement.forAdultContent(live.isAdult)) { liveCoordinator.enterLiveRoom(live.liveId) } } @@ -1033,70 +1018,6 @@ class CreatorChannelActivity : position == CreatorChannelTab.Donation.ordinal } - private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit) { - if (SharedPreferenceManager.token.isBlank()) { - showLoginActivity() - return - } - - if (isAdult) { - val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR" - if (isKoreanCountry && !SharedPreferenceManager.isAuth) { - V2ModalDialog( - activity = this, - layoutInflater = layoutInflater, - title = getString(R.string.auth_title), - desc = getString(R.string.auth_desc_live), - confirmButtonTitle = getString(R.string.auth_go), - confirmButtonClick = { startAuthFlow() }, - cancelButtonTitle = getString(R.string.cancel), - cancelButtonClick = {} - ).show(screenWidth) - return - } - - if (!SharedPreferenceManager.isAdultContentVisible) { - startActivity( - Intent(this, ContentSettingsActivity::class.java).apply { - putExtra(Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE, true) - } - ) - return - } - } - - onAuthed() - } - - private fun showLoginActivity() { - if (SharedPreferenceManager.token.isBlank()) { - startActivity( - Intent(this, LoginActivity::class.java).apply { - putExtra(Constants.EXTRA_DATA, intent.extras) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - } - ) - } - } - - private fun startAuthFlow() { - Auth.auth(this, this) { json -> - val bootpayResponse = Gson().fromJson(json, BootpayResponse::class.java) - val request = AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId) - runOnUiThread { - myPageViewModel.authVerify(request) { - startActivity( - Intent(this, SplashActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) - } - ) - finish() - } - } - } - } - private fun updateViewPagerHeight(afterHeightUpdated: (() -> Unit)? = null) { updateCreatorChannelTabViewportHeight() binding.viewPager.post { 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 e3432306..35554d3a 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,31 +47,82 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("marginStart = if (isChatVisible && isDmVisible)")) } + @Test + fun `Activity 진입은 creator id 확인 후 로그인 가드를 거부하면 화면 조립 전에 종료한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" + ).readText() + val setupSource = sourceSection( + source = source, + startMarker = "override fun setupView()", + endMarker = "private fun setupLiveEntryObservers" + ) + val invalidId = "if (creatorId <= 0L)" + val loginGuard = "if (!ensureV2Access(AccessRequirement.Login))" + val setupTabs = "setupTabsAndPager()" + + assertTrue(setupSource.contains(invalidId)) + assertTrue(setupSource.contains(loginGuard)) + assertTrue(setupSource.indexOf(invalidId) < setupSource.indexOf(loginGuard)) + assertTrue(setupSource.indexOf(loginGuard) < setupSource.indexOf(setupTabs)) + + val loginDeniedSource = setupSource.substring( + setupSource.indexOf(loginGuard), + setupSource.indexOf(setupTabs) + ) + assertTrue(loginDeniedSource.contains("finish()")) + assertTrue(loginDeniedSource.contains("return")) + } + + @Test + fun `AI 채팅 클릭은 성인 가드 통과 후 채팅방 생성을 요청한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" + ).readText() + val clickSource = sourceSection( + source = source, + startMarker = "binding.tvChatButton.setOnClickListener", + endMarker = "binding.tvDmButton.setOnClickListener" + ) + val adultGuard = "ensureV2Access(AccessRequirement.AdultContent)" + val createChatRoom = "homeActionDelegate?.createChatRoom(characterId)" + + assertTrue(clickSource.contains(adultGuard)) + assertTrue(clickSource.contains(createChatRoom)) + assertTrue(clickSource.indexOf(adultGuard) < clickSource.indexOf(createChatRoom)) + } + @Test fun `커뮤니티 게시물 클릭 source는 상세 Activity newIntent로 진입하고 invalid postId를 무시한다`() { val source = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" ).readText() + val handlerStart = source.indexOf("override fun onCreatorChannelCommunityPostClicked(postId: Long)") + val handlerEnd = source.indexOf("override fun onCreatorChannelFanTalkContentChanged", handlerStart) + + assertTrue(handlerStart >= 0) + assertTrue(handlerEnd > handlerStart) + val handlerSource = source.substring(handlerStart, handlerEnd) 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(handlerSource.contains("if (postId <= 0L) return")) + assertTrue(handlerSource.contains("ensureV2Access(AccessRequirement.Login)")) + assertTrue(handlerSource.contains("CreatorChannelCommunityDetailActivity.newIntent(this, postId)")) assertTrue(source.contains("private val communityDetailLauncher = registerForActivityResult")) assertTrue( - source.contains( + handlerSource.contains( "communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))" ) ) assertTrue( - source.indexOf("if (SharedPreferenceManager.token.isBlank())") < - source.indexOf("communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))") + handlerSource.indexOf("ensureV2Access(AccessRequirement.Login)") < + handlerSource.indexOf( + "communityDetailLauncher.launch(CreatorChannelCommunityDetailActivity.newIntent(this, postId))" + ) ) assertTrue(source.contains("refreshCreatorChannelCommunity()")) } @@ -207,6 +258,11 @@ class CreatorChannelActivitySourceTest { "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt" ).readText() val appDi = projectFile("app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt").readText() + val writeSource = sourceSection( + source = source, + startMarker = "private fun openFanTalkWrite()", + endMarker = "private fun onOwnerFabCommunityClicked" + ) assertTrue(layout.contains("android:id=\"@+id/btn_creator_channel_fantalk_fixed_plus\"")) assertTrue(layout.contains("android:src=\"@drawable/ic_plus_no_bg\"")) @@ -214,11 +270,13 @@ class CreatorChannelActivitySourceTest { assertTrue(layout.contains("app:layout_constraintBottom_toBottomOf=\"parent\"")) assertTrue(layout.contains("app:layout_constraintEnd_toEndOf=\"parent\"")) assertTrue(source.contains("private val fanTalkWriteLauncher = registerForActivityResult")) - assertTrue(source.contains("CreatorChannelFanTalkWriteActivity.newIntent(this, creatorId)")) - assertTrue(source.contains("private fun openFanTalkWrite()")) - assertTrue(source.contains("if (creatorId <= 0L) return")) - assertTrue(source.contains("if (SharedPreferenceManager.token.isBlank())")) - assertTrue(source.contains("showLoginActivity()")) + assertTrue(writeSource.contains("if (creatorId <= 0L) return")) + assertTrue(writeSource.contains("ensureV2Access(AccessRequirement.Login)")) + assertTrue(writeSource.contains("CreatorChannelFanTalkWriteActivity.newIntent(this, creatorId)")) + assertTrue( + writeSource.indexOf("ensureV2Access(AccessRequirement.Login)") < + writeSource.indexOf("fanTalkWriteLauncher.launch") + ) assertTrue(source.contains("refreshCreatorChannelFanTalk()")) assertTrue(source.contains("homeActionDelegate?.refreshHome()")) assertTrue(source.contains("onCreatorChannelFanTalkWriteClicked()")) @@ -1474,15 +1532,18 @@ class CreatorChannelActivitySourceTest { assertTrue(fragment.contains("private fun onCurrentLiveClicked(live: CreatorChannelLiveResponse)")) assertTrue(fragment.contains("host.onCreatorChannelCurrentLiveClicked(live)")) assertTrue(activity.contains("override fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse)")) - assertTrue(activity.contains("ensureLoginAndAdultAuth(isAdult = live.isAdult)")) + assertTrue(activity.contains("ensureV2Access(AccessRequirement.forAdultContent(live.isAdult))")) assertTrue(activity.contains("liveCoordinator.enterLiveRoom(live.liveId)")) - assertTrue(activity.contains("private fun ensureLoginAndAdultAuth(isAdult: Boolean, onAuthed: () -> Unit)")) - assertTrue(activity.contains("SharedPreferenceManager.token.isBlank()")) - assertTrue(activity.contains("V2ModalDialog(")) - assertFalse(activity.contains("SodaDialog(")) - assertTrue(activity.contains("R.string.auth_desc_live")) - assertTrue(activity.contains("ContentSettingsActivity::class.java")) - assertTrue(activity.contains("Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE")) + assertFalse(activity.contains("private fun ensureLoginAndAdultAuth")) + assertTrue(activity.contains("ensureV2Access(AccessRequirement.forAdultContent(live.isAdult))")) + val guard = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuard.kt").readText() + assertTrue(guard.contains("V2ModalDialog(")) + assertFalse(guard.contains("SodaDialog(")) + assertTrue(guard.contains("R.string.auth_desc_live")) + assertTrue(guard.contains("ContentSettingsActivity::class.java")) + assertTrue(guard.contains("Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE")) + assertFalse(activity.contains("ContentSettingsActivity::class.java")) + assertFalse(activity.contains("Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE")) } @Test @@ -1891,7 +1952,6 @@ class CreatorChannelActivitySourceTest { assertTrue(fanTalkLayout.contains("android:layout_width=\"match_parent\"")) assertTrue(fanTalkLayout.contains("android:layout_height=\"169dp\"")) assertTrue(fanTalkLayout.contains("android:layout_marginBottom=\"@dimen/spacing_6\"")) - assertTrue(fanTalkLayout.contains("@drawable/ic_chevron_down_white")) assertFalse(fanTalkLayout.contains("@+id/ll_section_items")) assertFalse(fanTalkLayout.contains("android:clipToOutline")) assertTrue(fanTalkCardView.contains("clipToOutline = true")) @@ -2186,21 +2246,39 @@ class CreatorChannelActivitySourceTest { ) ) assertTrue(source.contains("private val fanTalkDetailLauncher = registerForActivityResult")) - assertTrue(source.contains("override fun onCreatorChannelFanTalkItemClicked(item: CreatorChannelFanTalkUiModel)")) - assertTrue(source.contains("if (currentHeader?.isOwner != true) return")) - assertTrue(source.contains("if (creatorId <= 0L || item.fanTalkId <= 0L) return")) - assertTrue(source.contains("if (SharedPreferenceManager.token.isBlank())")) - assertTrue(source.contains("showLoginActivity()")) - assertTrue(source.contains("fanTalkDetailLauncher.launch(")) + val handlerSource = sourceSection( + source = source, + startMarker = "override fun onCreatorChannelFanTalkItemClicked(item: CreatorChannelFanTalkUiModel)", + endMarker = "override fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged" + ) + + assertTrue(handlerSource.contains("if (currentHeader?.isOwner != true) return")) + assertTrue(handlerSource.contains("if (creatorId <= 0L || item.fanTalkId <= 0L) return")) + assertTrue(handlerSource.contains("ensureV2Access(AccessRequirement.Login)")) + assertTrue(handlerSource.contains("fanTalkDetailLauncher.launch(")) assertTrue( - source.contains( + handlerSource.contains( "CreatorChannelFanTalkDetailActivity.newIntent(this, creatorId, item.toFanTalkDetailPayload())" ) ) + assertTrue( + handlerSource.indexOf("ensureV2Access(AccessRequirement.Login)") < + handlerSource.indexOf("fanTalkDetailLauncher.launch(") + ) assertTrue(source.contains("refreshCreatorChannelFanTalk()")) assertTrue(source.contains("homeActionDelegate?.refreshHome()")) } + private fun sourceSection(source: String, startMarker: String, endMarker: String): String { + val start = source.indexOf(startMarker) + val end = source.indexOf(endMarker, start) + + assertTrue("start marker not found: $startMarker", start >= 0) + assertTrue("end marker not found after: $startMarker", end > start) + + return source.substring(start, end) + } + private fun projectFile(relativePath: String): File { val candidates = listOf(File(relativePath), File("../$relativePath")) return candidates.firstOrNull { it.exists() }