fix(home): 메인 홈 추천 탭 지금 라이브 중 입장을 연결한다
This commit is contained in:
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||||
@@ -27,7 +28,7 @@ import java.util.Locale
|
|||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
class CreatorChannelLiveCoordinator(
|
class CreatorChannelLiveCoordinator(
|
||||||
private val activity: CreatorChannelActivity,
|
private val activity: FragmentActivity,
|
||||||
private val layoutInflater: LayoutInflater,
|
private val layoutInflater: LayoutInflater,
|
||||||
private val fragmentManager: FragmentManager,
|
private val fragmentManager: FragmentManager,
|
||||||
private val liveViewModel: LiveViewModel,
|
private val liveViewModel: LiveViewModel,
|
||||||
@@ -65,38 +66,64 @@ class CreatorChannelLiveCoordinator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
liveViewModel.getRoomDetail(roomId) {
|
liveViewModel.getRoomDetail(roomId) {
|
||||||
if (!it.channelName.isNullOrBlank()) {
|
enterLiveRoom(roomId, it, onEnterRoomSuccess)
|
||||||
if (it.manager.id == SharedPreferenceManager.userId) {
|
}
|
||||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
}
|
||||||
} else if (it.price == 0 || it.isPaid) {
|
|
||||||
if (it.isPrivateRoom) {
|
fun enterLiveRoom(roomId: Long, roomDetail: GetRoomDetailResponse) {
|
||||||
LiveRoomPasswordDialog(
|
activity.startService(
|
||||||
activity = activity,
|
Intent(activity, AudioContentPlayService::class.java).apply {
|
||||||
layoutInflater = layoutInflater,
|
action = AudioContentPlayService.MusicAction.STOP.name
|
||||||
can = 0,
|
}
|
||||||
confirmButtonClick = { password ->
|
)
|
||||||
liveViewModel.enterRoom(
|
activity.startService(
|
||||||
roomId = roomId,
|
Intent(activity, AudioContentPlayerService::class.java).apply {
|
||||||
onSuccess = onEnterRoomSuccess,
|
action = "STOP_SERVICE"
|
||||||
password = password
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
).show(screenWidthProvider())
|
val onEnterRoomSuccess = {
|
||||||
} else {
|
activity.runOnUiThread { openLiveRoom(roomId) }
|
||||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
}
|
||||||
}
|
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 (roomDetail.price == 0 || roomDetail.isPaid) {
|
||||||
|
if (roomDetail.isPrivateRoom) {
|
||||||
|
LiveRoomPasswordDialog(
|
||||||
|
activity = activity,
|
||||||
|
layoutInflater = layoutInflater,
|
||||||
|
can = 0,
|
||||||
|
confirmButtonClick = { password ->
|
||||||
|
liveViewModel.enterRoom(
|
||||||
|
roomId = roomId,
|
||||||
|
onSuccess = onEnterRoomSuccess,
|
||||||
|
password = password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).show(screenWidthProvider())
|
||||||
} else {
|
} else {
|
||||||
showPaidLiveEntryDialog(
|
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||||
roomId = roomId,
|
|
||||||
beginDateTimeUtc = it.beginDateTimeUtc,
|
|
||||||
price = it.price,
|
|
||||||
isPrivateRoom = it.isPrivateRoom,
|
|
||||||
onEnterRoomSuccess = onEnterRoomSuccess
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showLiveRoomDetail(roomId)
|
showPaidLiveEntryDialog(
|
||||||
|
roomId = roomId,
|
||||||
|
beginDateTimeUtc = roomDetail.beginDateTimeUtc,
|
||||||
|
price = roomDetail.price,
|
||||||
|
isPrivateRoom = roomDetail.isPrivateRoom,
|
||||||
|
onEnterRoomSuccess = onEnterRoomSuccess
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
showLiveRoomDetail(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.following.FollowingCreatorActivity
|
||||||
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.home.pushnotification.PushNotificationListActivity
|
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.mypage.can.charge.CanChargeActivity
|
||||||
import kr.co.vividnext.sodalive.search.SearchActivity
|
import kr.co.vividnext.sodalive.search.SearchActivity
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity
|
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.live.onair.HomeOnAirLiveActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
|
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.CreatorRankingAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItem
|
import kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingItem
|
||||||
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
||||||
@@ -82,7 +85,18 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
private val homeRecommendationViewModel: HomeRecommendationViewModel by viewModel()
|
private val homeRecommendationViewModel: HomeRecommendationViewModel by viewModel()
|
||||||
private val homeCreatorRankingViewModel: HomeCreatorRankingViewModel by viewModel()
|
private val homeCreatorRankingViewModel: HomeCreatorRankingViewModel by viewModel()
|
||||||
private val homeFollowingViewModel: HomeFollowingViewModel 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 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 liveAdapter = HomeLiveAdapter()
|
||||||
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
|
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
|
||||||
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
|
||||||
@@ -111,6 +125,10 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
private var hasLoadedCreatorRankings = false
|
private var hasLoadedCreatorRankings = false
|
||||||
private var hasLoadedFollowing = false
|
private var hasLoadedFollowing = false
|
||||||
private var currentHomeTabIndex = HOME_TAB_RECOMMENDATION
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
@@ -138,6 +156,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
bindHomeRecommendationObservers()
|
bindHomeRecommendationObservers()
|
||||||
bindHomeCreatorRankingObservers()
|
bindHomeCreatorRankingObservers()
|
||||||
bindHomeFollowingObservers()
|
bindHomeFollowingObservers()
|
||||||
|
bindLiveEntryObservers()
|
||||||
homeRecommendationViewModel.loadRecommendations()
|
homeRecommendationViewModel.loadRecommendations()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,11 +271,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeCreatorRankingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
homeCreatorRankingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
if (isLoading) {
|
isCreatorRankingLoading = isLoading
|
||||||
loadingDialog.show(screenWidth)
|
updateLoadingDialog()
|
||||||
} else {
|
|
||||||
loadingDialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
homeCreatorRankingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
homeCreatorRankingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
||||||
toastMessage?.let(::showToast)
|
toastMessage?.let(::showToast)
|
||||||
@@ -315,11 +331,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeFollowingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
homeFollowingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
if (isLoading) {
|
isFollowingLoading = isLoading
|
||||||
loadingDialog.show(screenWidth)
|
updateLoadingDialog()
|
||||||
} else {
|
|
||||||
loadingDialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
homeFollowingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
homeFollowingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
||||||
toastMessage?.let(::showToast)
|
toastMessage?.let(::showToast)
|
||||||
@@ -337,17 +350,32 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeRecommendationViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
homeRecommendationViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
if (isLoading) {
|
isRecommendationLoading = isLoading
|
||||||
loadingDialog.show(screenWidth)
|
updateLoadingDialog()
|
||||||
} else {
|
|
||||||
loadingDialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
homeRecommendationViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
homeRecommendationViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
|
||||||
toastMessage?.let(::showToast)
|
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) {
|
private fun bindHomeRecommendationContent(content: HomeRecommendationUiState.Content) {
|
||||||
bindLiveSection(content.lives)
|
bindLiveSection(content.lives)
|
||||||
bindBannerSection(content.banners)
|
bindBannerSection(content.banners)
|
||||||
@@ -514,7 +542,16 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
ivSectionTitleChevron.visibility = if (showMore) View.VISIBLE else View.GONE
|
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() {
|
private fun openHomeOnAirLive() {
|
||||||
ensureMainV2NavigationAllowed {
|
ensureMainV2NavigationAllowed {
|
||||||
|
|||||||
@@ -77,6 +77,46 @@ class HomeMainFragmentLoginGuardSourceTest {
|
|||||||
assertTrue(showHomeTabSource.contains("currentHomeTabIndex = index"))
|
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) {
|
private fun assertGuardedStartActivity(source: String, functionSignature: String) {
|
||||||
val functionSource = source.substringFrom(functionSignature)
|
val functionSource = source.substringFrom(functionSignature)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
@@ -101,6 +141,14 @@ class HomeMainFragmentLoginGuardSourceTest {
|
|||||||
assertTrue("Missing source after $expectedBefore: $expectedAfter", afterIndex > beforeIndex)
|
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 {
|
private fun String.substringFrom(marker: String): String {
|
||||||
val startIndex = indexOf(marker)
|
val startIndex = indexOf(marker)
|
||||||
assertTrue("Missing function: $marker", startIndex >= 0)
|
assertTrue("Missing function: $marker", startIndex >= 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user