fix(home): 메인 홈 추천 탭 지금 라이브 중 입장을 연결한다

This commit is contained in:
2026-07-06 02:34:41 +09:00
parent b1f605fda4
commit 5489c86cdc
3 changed files with 157 additions and 45 deletions

View File

@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel
import android.content.Intent
import android.view.LayoutInflater
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
@@ -27,7 +28,7 @@ import java.util.Locale
import java.util.TimeZone
class CreatorChannelLiveCoordinator(
private val activity: CreatorChannelActivity,
private val activity: FragmentActivity,
private val layoutInflater: LayoutInflater,
private val fragmentManager: FragmentManager,
private val liveViewModel: LiveViewModel,
@@ -65,11 +66,38 @@ class CreatorChannelLiveCoordinator(
}
liveViewModel.getRoomDetail(roomId) {
if (!it.channelName.isNullOrBlank()) {
if (it.manager.id == SharedPreferenceManager.userId) {
enterLiveRoom(roomId, it, onEnterRoomSuccess)
}
}
fun enterLiveRoom(roomId: Long, roomDetail: GetRoomDetailResponse) {
activity.startService(
Intent(activity, AudioContentPlayService::class.java).apply {
action = AudioContentPlayService.MusicAction.STOP.name
}
)
activity.startService(
Intent(activity, AudioContentPlayerService::class.java).apply {
action = "STOP_SERVICE"
}
)
val onEnterRoomSuccess = {
activity.runOnUiThread { openLiveRoom(roomId) }
}
enterLiveRoom(roomId, roomDetail, onEnterRoomSuccess)
}
private fun enterLiveRoom(
roomId: Long,
roomDetail: GetRoomDetailResponse,
onEnterRoomSuccess: () -> Unit
) {
if (!roomDetail.channelName.isNullOrBlank()) {
if (roomDetail.manager.id == SharedPreferenceManager.userId) {
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
} else if (it.price == 0 || it.isPaid) {
if (it.isPrivateRoom) {
} else if (roomDetail.price == 0 || roomDetail.isPaid) {
if (roomDetail.isPrivateRoom) {
LiveRoomPasswordDialog(
activity = activity,
layoutInflater = layoutInflater,
@@ -88,9 +116,9 @@ class CreatorChannelLiveCoordinator(
} else {
showPaidLiveEntryDialog(
roomId = roomId,
beginDateTimeUtc = it.beginDateTimeUtc,
price = it.price,
isPrivateRoom = it.isPrivateRoom,
beginDateTimeUtc = roomDetail.beginDateTimeUtc,
price = roomDetail.price,
isPrivateRoom = roomDetail.isPrivateRoom,
onEnterRoomSuccess = onEnterRoomSuccess
)
}
@@ -98,7 +126,6 @@ class CreatorChannelLiveCoordinator(
showLiveRoomDetail(roomId)
}
}
}
private fun reservationRoom(roomId: Long) {
liveViewModel.getRoomDetail(roomId) {

View File

@@ -17,9 +17,11 @@ import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCo
import kr.co.vividnext.sodalive.following.FollowingCreatorActivity
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.home.pushnotification.PushNotificationListActivity
import kr.co.vividnext.sodalive.live.LiveViewModel
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
import kr.co.vividnext.sodalive.search.SearchActivity
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelLiveCoordinator
import kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveActivity
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
@@ -74,6 +76,7 @@ import kr.co.vividnext.sodalive.v2.main.home.ui.HomeRecentDebutCreatorAdapter
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingAdapter
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItem
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
@@ -82,7 +85,18 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
private val homeRecommendationViewModel: HomeRecommendationViewModel by viewModel()
private val homeCreatorRankingViewModel: HomeCreatorRankingViewModel by viewModel()
private val homeFollowingViewModel: HomeFollowingViewModel by viewModel()
private val liveViewModel: LiveViewModel by inject()
private val loadingDialog: LoadingDialog by lazy { LoadingDialog(requireActivity(), layoutInflater) }
private val liveCoordinator: CreatorChannelLiveCoordinator by lazy {
CreatorChannelLiveCoordinator(
activity = requireActivity(),
layoutInflater = layoutInflater,
fragmentManager = requireActivity().supportFragmentManager,
liveViewModel = liveViewModel,
screenWidthProvider = { screenWidth },
refreshHome = { homeRecommendationViewModel.loadRecommendations() }
)
}
private val liveAdapter = HomeLiveAdapter()
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
@@ -111,6 +125,10 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
private var hasLoadedCreatorRankings = false
private var hasLoadedFollowing = false
private var currentHomeTabIndex = HOME_TAB_RECOMMENDATION
private var isRecommendationLoading = false
private var isCreatorRankingLoading = false
private var isFollowingLoading = false
private var isLiveEntryLoading = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -138,6 +156,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
bindHomeRecommendationObservers()
bindHomeCreatorRankingObservers()
bindHomeFollowingObservers()
bindLiveEntryObservers()
homeRecommendationViewModel.loadRecommendations()
}
@@ -252,11 +271,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
}
}
homeCreatorRankingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
if (isLoading) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
isCreatorRankingLoading = isLoading
updateLoadingDialog()
}
homeCreatorRankingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
toastMessage?.let(::showToast)
@@ -315,11 +331,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
}
}
homeFollowingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
if (isLoading) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
isFollowingLoading = isLoading
updateLoadingDialog()
}
homeFollowingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
toastMessage?.let(::showToast)
@@ -337,17 +350,32 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
}
}
homeRecommendationViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
if (isLoading) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
isRecommendationLoading = isLoading
updateLoadingDialog()
}
homeRecommendationViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
toastMessage?.let(::showToast)
}
}
private fun bindLiveEntryObservers() {
liveViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
isLiveEntryLoading = isLoading
updateLoadingDialog()
}
liveViewModel.toastLiveData.observe(viewLifecycleOwner) { message ->
message?.let { showToast(it) }
}
}
private fun updateLoadingDialog() {
if (isRecommendationLoading || isCreatorRankingLoading || isFollowingLoading || isLiveEntryLoading) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
}
private fun bindHomeRecommendationContent(content: HomeRecommendationUiState.Content) {
bindLiveSection(content.lives)
bindBannerSection(content.banners)
@@ -514,7 +542,16 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
ivSectionTitleChevron.visibility = if (showMore) View.VISIBLE else View.GONE
}
private fun onLiveClick(item: HomeRecommendationLiveUiModel) = Unit
private fun onLiveClick(item: HomeRecommendationLiveUiModel) {
ensureMainV2NavigationAllowed {
liveViewModel.getRoomDetail(item.roomId) { roomDetail ->
if (!isAdded || view == null) return@getRoomDetail
ensureMainV2NavigationAllowed(requiresAdultContentAccess = roomDetail.isAdult) {
liveCoordinator.enterLiveRoom(item.roomId, roomDetail)
}
}
}
}
private fun openHomeOnAirLive() {
ensureMainV2NavigationAllowed {

View File

@@ -77,6 +77,46 @@ class HomeMainFragmentLoginGuardSourceTest {
assertTrue(showHomeTabSource.contains("currentHomeTabIndex = index"))
}
@Test
fun `HomeMainFragment 추천 라이브 아이템은 로그인과 성인 가드 후 기존 입장 흐름을 실행한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt"
).readText()
val clickSource = source.substringFrom("private fun onLiveClick(item: HomeRecommendationLiveUiModel)")
assertTrue(clickSource.contains("ensureMainV2NavigationAllowed"))
assertTrue(clickSource.contains("liveViewModel.getRoomDetail(item.roomId) { roomDetail ->"))
assertBefore(clickSource, "if (!isAdded || view == null) return@getRoomDetail", "requiresAdultContentAccess = roomDetail.isAdult")
assertBefore(clickSource, "liveViewModel.getRoomDetail(item.roomId) { roomDetail ->", "requiresAdultContentAccess = roomDetail.isAdult")
assertBefore(clickSource, "requiresAdultContentAccess = roomDetail.isAdult", "liveCoordinator.enterLiveRoom(item.roomId, roomDetail)")
assertFalse(clickSource.contains("= Unit"))
assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelLiveCoordinator"))
assertTrue(source.contains("private val liveCoordinator: CreatorChannelLiveCoordinator by lazy"))
}
@Test
fun `HomeMainFragment loading dialog는 통합 loading state로 제어한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt"
).readText()
assertTrue(source.contains("private var isRecommendationLoading = false"))
assertTrue(source.contains("private var isCreatorRankingLoading = false"))
assertTrue(source.contains("private var isFollowingLoading = false"))
assertTrue(source.contains("private var isLiveEntryLoading = false"))
val updateSource = source.substringFrom("private fun updateLoadingDialog()")
assertTrue(updateSource.contains("isRecommendationLoading || isCreatorRankingLoading || isFollowingLoading || isLiveEntryLoading"))
assertTrue(updateSource.contains("loadingDialog.show(screenWidth)"))
assertTrue(updateSource.contains("loadingDialog.dismiss()"))
assertLoadingObserverUsesUpdate(source, "homeRecommendationViewModel.isLoading.observe(viewLifecycleOwner)", "isRecommendationLoading = isLoading")
assertLoadingObserverUsesUpdate(source, "homeCreatorRankingViewModel.isLoading.observe(viewLifecycleOwner)", "isCreatorRankingLoading = isLoading")
assertLoadingObserverUsesUpdate(source, "homeFollowingViewModel.isLoading.observe(viewLifecycleOwner)", "isFollowingLoading = isLoading")
assertLoadingObserverUsesUpdate(source, "liveViewModel.isLoading.observe(viewLifecycleOwner)", "isLiveEntryLoading = isLoading")
}
private fun assertGuardedStartActivity(source: String, functionSignature: String) {
val functionSource = source.substringFrom(functionSignature)
assertTrue(
@@ -101,6 +141,14 @@ class HomeMainFragmentLoginGuardSourceTest {
assertTrue("Missing source after $expectedBefore: $expectedAfter", afterIndex > beforeIndex)
}
private fun assertLoadingObserverUsesUpdate(source: String, observerMarker: String, loadingAssignment: String) {
val observerSource = source.substringFrom(observerMarker)
assertTrue(observerSource.contains(loadingAssignment))
assertTrue(observerSource.contains("updateLoadingDialog()"))
assertFalse(observerSource.contains("loadingDialog.show(screenWidth)"))
assertFalse(observerSource.contains("loadingDialog.dismiss()"))
}
private fun String.substringFrom(marker: String): String {
val startIndex = indexOf(marker)
assertTrue("Missing function: $marker", startIndex >= 0)