diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt index 675dc018..f36815ec 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt @@ -10,11 +10,11 @@ import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowCompat import androidx.recyclerview.widget.LinearLayoutManager import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity @@ -22,6 +22,7 @@ import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.databinding.ActivityCreatorChannelHomeBinding +import kr.co.vividnext.sodalive.explorer.profile.CreatorFollowNotifyFragment import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.extensions.moneyFormat @@ -46,6 +47,8 @@ class CreatorChannelHomeActivity : BaseActivity viewModel.createChatRoom(characterId) } } @@ -103,6 +108,10 @@ class CreatorChannelHomeActivity : BaseActivity Toast.makeText(applicationContext, text, Toast.LENGTH_LONG).show() } } } + viewModel.isFollowInProgressLiveData.observe(this) { inProgress -> + isFollowInProgress = inProgress + currentHeader?.let(::bindTitleBar) + } } private fun bindContent(content: CreatorChannelHomeUiState.Content) { @@ -133,7 +142,7 @@ class CreatorChannelHomeActivity : BaseActivity) { binding.tabContainer.removeAllViews() - tabs.forEachIndexed { index, tab -> - binding.tabContainer.addView(createTabView(tab, isSelected = index == 0)) + tabs.forEach { tab -> + binding.tabContainer.addView(createTabView(tab, isSelected = tab == selectedTab)) } } @@ -195,9 +205,37 @@ class CreatorChannelHomeActivity : BaseActivity val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModel.kt index b46a6857..09b952af 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModel.kt @@ -29,6 +29,10 @@ class CreatorChannelHomeViewModel( val chatRoomIdLiveData: LiveData> get() = _chatRoomIdLiveData + private val _isFollowInProgressLiveData = MutableLiveData(false) + val isFollowInProgressLiveData: LiveData + get() = _isFollowInProgressLiveData + private var isFollowInProgress = false private var isCreateChatRoomInProgress = false @@ -62,6 +66,7 @@ class CreatorChannelHomeViewModel( if (isFollowInProgress) return isFollowInProgress = true + _isFollowInProgressLiveData.value = true compositeDisposable.add( repository.followCreator( creatorId = content.header.creatorId, @@ -74,6 +79,7 @@ class CreatorChannelHomeViewModel( .subscribe( { isFollowInProgress = false + _isFollowInProgressLiveData.value = false if (it.success) { _homeStateLiveData.value = content.copy( header = content.header.copy(isFollow = follow, isNotify = notify) @@ -84,6 +90,7 @@ class CreatorChannelHomeViewModel( }, { isFollowInProgress = false + _isFollowInProgressLiveData.value = false it.message?.let { message -> Logger.e(message) } showUnknownErrorToast() } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivitySourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivitySourceTest.kt index 356e6b22..277f0b24 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivitySourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivitySourceTest.kt @@ -48,7 +48,27 @@ class CreatorChannelHomeActivitySourceTest { assertTrue(source.contains("viewModel.createChatRoom(characterId)")) assertTrue(source.contains("updateActionButtonLayout")) assertTrue(source.contains("marginStart = if (isChatVisible && isDmVisible)")) - assertFalse(source.contains("CreatorFollowNotifyFragment")) + } + + @Test + fun `follow notify source는 미팔로우 직접 팔로우와 팔로우 중 알림 sheet를 연결한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt" + ).readText() + + assertTrue(source.contains("CreatorFollowNotifyFragment")) + assertTrue(source.contains("binding.layoutFollowCapsule.setOnClickListener")) + assertTrue(source.contains("binding.ivBell.setOnClickListener")) + assertTrue(source.contains("private fun onFollowActionClicked")) + assertTrue(source.contains("if (!header.isFollow)")) + assertTrue(source.contains("viewModel.follow(follow = true, notify = true)")) + assertTrue(source.contains("showFollowNotifyFragment")) + assertTrue(source.contains("onClickNotifyAll = { viewModel.follow(follow = true, notify = true) }")) + assertTrue(source.contains("onClickNotifyNone = { viewModel.follow(follow = true, notify = false) }")) + assertTrue(source.contains("onClickUnFollow = { viewModel.follow(follow = false, notify = false) }")) + assertTrue(source.contains("viewModel.isFollowInProgressLiveData.observe(this)")) + assertTrue(source.contains("binding.layoutFollowCapsule.isEnabled = titleBarState.isActionEnabled")) + assertTrue(source.contains("binding.ivBell.isEnabled = titleBarState.isActionEnabled")) } @Test @@ -132,13 +152,32 @@ class CreatorChannelHomeActivitySourceTest { "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt" ).readText() - assertTrue(source.contains("createTabView(tab, isSelected = index == 0)")) + assertTrue(source.contains("createTabView(tab, isSelected = tab == selectedTab)")) assertTrue(source.contains("tabText.textSize = 16f")) assertTrue(source.contains("width = 110.dpToPx().toInt()")) assertTrue(source.contains("indicator.setBackgroundColor(getColor(R.color.soda_400))")) assertTrue(source.contains("indicator.isVisible = isSelected")) } + @Test + fun `tab source는 홈 기본 선택과 홈 외 탭 no op 정책을 명시한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt" + ).readText() + + assertTrue(source.contains("private var selectedTab: CreatorChannelTab = CreatorChannelTab.Home")) + assertTrue(source.contains("createTabView(tab, isSelected = tab == selectedTab)")) + assertTrue(source.contains("setOnClickListener { onTabClicked(tab) }")) + assertTrue(source.contains("private fun onTabClicked(tab: CreatorChannelTab)")) + assertTrue(source.contains("if (tab != CreatorChannelTab.Home) return")) + assertFalse(source.contains("CreatorChannelTab.Live ->")) + assertFalse(source.contains("CreatorChannelTab.Audio ->")) + assertFalse(source.contains("CreatorChannelTab.Series ->")) + assertFalse(source.contains("CreatorChannelTab.Community ->")) + assertFalse(source.contains("CreatorChannelTab.FanTalk ->")) + assertFalse(source.contains("CreatorChannelTab.Donation ->")) + } + @Test fun `section adapter source는 활동 지표를 행 단위 resource label로 표시한다`() { val adapter = projectFile( @@ -940,6 +979,27 @@ class CreatorChannelHomeActivitySourceTest { assertTrue(manifest.contains(".v2.creator.channel.CreatorChannelHomeActivity")) } + @Test + fun `기존 크리에이터 채널 진입점은 UserProfileActivity 대신 CreatorChannelHomeActivity로 이동한다`() { + val sourceRoot = projectFile("app/src/main/java") + val directUserProfileRoutes = sourceRoot + .walkTopDown() + .filter { it.isFile && it.extension in setOf("kt", "java") } + .filterNot { it.name == "UserProfileActivity.kt" } + .filter { file -> + val source = file.readText() + source.contains("UserProfileActivity::class.java") || + source.contains("import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity") + } + .map { it.relativeTo(sourceRoot).path } + .toList() + + assertTrue( + "UserProfileActivity direct routes remain: $directUserProfileRoutes", + directUserProfileRoutes.isEmpty() + ) + } + @Test fun `채팅과 DM Activity intent helper 계약을 참조한다`() { val chatRoom = projectFile("app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt").readText() diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModelTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModelTest.kt index 81cda6f9..999d15e0 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModelTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeViewModelTest.kt @@ -149,8 +149,10 @@ class CreatorChannelHomeViewModelTest { viewModel.follow(follow = false, notify = false) viewModel.follow(follow = false, notify = false) + assertEquals(true, viewModel.isFollowInProgressLiveData.requireValue()) verify(repository, times(1)).followCreator(100L, false, false, "Bearer test-token") pending.onSuccess(ApiResponse(true, Any(), null)) + assertEquals(false, viewModel.isFollowInProgressLiveData.requireValue()) } @Test