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 e7070f5e..4e33fafa 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 @@ -71,6 +71,9 @@ import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelScrollSta import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState import kr.co.vividnext.sodalive.v2.creator.channel.series.CreatorChannelSeriesFragment +import kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinator +import kr.co.vividnext.sodalive.v2.live.action.LiveCreationResult +import kr.co.vividnext.sodalive.v2.live.action.resolveLiveCreationResult import kr.co.vividnext.sodalive.v2.main.MainV2Activity import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity import okhttp3.MediaType.Companion.toMediaType @@ -102,8 +105,8 @@ class CreatorChannelActivity : private var isFanTalkFixedPlusVisible: Boolean = false private lateinit var loadingDialog: LoadingDialog private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() } - private val liveCoordinator: CreatorChannelLiveCoordinator by lazy { - CreatorChannelLiveCoordinator( + private val liveActionCoordinator: LiveActionCoordinator by lazy { + LiveActionCoordinator( activity = this, layoutInflater = layoutInflater, fragmentManager = supportFragmentManager, @@ -153,15 +156,28 @@ class CreatorChannelActivity : private val liveRoomCreateLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> - if (result.resultCode == RESULT_OK) { - homeActionDelegate?.refreshHome() - val roomId = result.data?.getLongExtra(Constants.EXTRA_ROOM_ID, 0L) - val channelName = result.data?.getStringExtra(Constants.EXTRA_ROOM_CHANNEL_NAME) - if (channelName != null) { - roomId?.takeIf { it > 0L }?.let(liveCoordinator::enterLiveRoom) - } else { + handleLiveCreationResult( + resolveLiveCreationResult( + isSuccessful = result.resultCode == RESULT_OK, + roomId = result.data?.getLongExtra(Constants.EXTRA_ROOM_ID, 0L), + channelName = result.data?.getStringExtra(Constants.EXTRA_ROOM_CHANNEL_NAME) + ) + ) + } + + private fun handleLiveCreationResult(result: LiveCreationResult) { + when (result) { + LiveCreationResult.Ignored -> Unit + LiveCreationResult.RefreshOnly -> homeActionDelegate?.refreshHome() + LiveCreationResult.Created -> { + homeActionDelegate?.refreshHome() showToast(getString(R.string.creator_channel_live_created_message)) } + + is LiveCreationResult.Enter -> { + homeActionDelegate?.refreshHome() + liveActionCoordinator.enterLiveRoom(result.roomId) + } } } @@ -961,9 +977,7 @@ class CreatorChannelActivity : } override fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse) { - ensureV2Access(AccessRequirement.forAdultContent(live.isAdult)) { - liveCoordinator.enterLiveRoom(live.liveId) - } + liveActionCoordinator.enterLiveRoom(live.liveId, requiresAdultContentAccess = live.isAdult) } override fun onCreatorChannelLiveReplayClicked( @@ -1093,7 +1107,7 @@ class CreatorChannelActivity : CreatorActivityType.Audio, CreatorActivityType.LiveReplay -> openAudioContentDetail(schedule.targetId) - CreatorActivityType.Live -> liveCoordinator.showLiveRoomDetail(schedule.targetId) + CreatorActivityType.Live -> liveActionCoordinator.showLiveRoomDetail(schedule.targetId) CreatorActivityType.Community -> Unit } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt deleted file mode 100644 index 71c86117..00000000 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt +++ /dev/null @@ -1,276 +0,0 @@ -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 -import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService -import kr.co.vividnext.sodalive.common.Constants -import kr.co.vividnext.sodalive.common.SharedPreferenceManager -import kr.co.vividnext.sodalive.extensions.moneyFormat -import kr.co.vividnext.sodalive.live.LiveViewModel -import kr.co.vividnext.sodalive.live.reservation.complete.LiveReservationCompleteActivity -import kr.co.vividnext.sodalive.live.room.LiveRoomActivity -import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse -import kr.co.vividnext.sodalive.live.room.detail.LiveRoomDetailFragment -import kr.co.vividnext.sodalive.live.room.dialog.LiveCancelDialog -import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog -import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog -import kr.co.vividnext.sodalive.live.room.update.LiveRoomEditActivity -import kr.co.vividnext.sodalive.settings.language.LanguageManager -import kr.co.vividnext.sodalive.settings.language.LocaleHelper -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import java.util.TimeZone - -class CreatorChannelLiveCoordinator( - private val activity: FragmentActivity, - private val layoutInflater: LayoutInflater, - private val fragmentManager: FragmentManager, - private val liveViewModel: LiveViewModel, - private val screenWidthProvider: () -> Int, - private val refreshHome: () -> Unit -) { - fun showLiveRoomDetail(roomId: Long) { - val detailFragment = LiveRoomDetailFragment( - roomId, - onClickParticipant = {}, - onClickReservation = { reservationRoom(roomId) }, - onClickModify = { roomDetailResponse -> modifyLive(roomDetailResponse) }, - onClickStart = { startLive(roomId) }, - onClickCancel = { cancelLive(roomId) } - ) - if (detailFragment.isAdded) return - - detailFragment.show(fragmentManager, detailFragment.tag) - } - - fun enterLiveRoom(roomId: Long) { - 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) } - } - - liveViewModel.getRoomDetail(roomId) { - 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 (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 { - liveViewModel.enterRoom(roomId, onEnterRoomSuccess) - } - } else { - showPaidLiveEntryDialog( - roomId = roomId, - beginDateTimeUtc = roomDetail.beginDateTimeUtc, - price = roomDetail.price, - isPrivateRoom = roomDetail.isPrivateRoom, - onEnterRoomSuccess = onEnterRoomSuccess - ) - } - } else { - showLiveRoomDetail(roomId) - } - } - - private fun reservationRoom(roomId: Long) { - liveViewModel.getRoomDetail(roomId) { - if (it.manager.id == SharedPreferenceManager.userId) { - Toast.makeText( - activity, - activity.getString(R.string.screen_live_reservation_self_block), - Toast.LENGTH_LONG - ).show() - return@getRoomDetail - } - - if (it.isPrivateRoom) { - LiveRoomPasswordDialog( - activity = activity, - layoutInflater = layoutInflater, - can = if (it.isPaid) 0 else it.price, - confirmButtonClick = { password -> processLiveReservation(roomId, password) } - ).show(screenWidthProvider()) - } else if (it.price == 0 || it.isPaid) { - processLiveReservation(roomId) - } else { - LivePaymentDialog( - activity = activity, - layoutInflater = layoutInflater, - title = activity.getString( - R.string.screen_live_reservation_pay_title, - it.price.moneyFormat() - ), - desc = activity.getString(R.string.screen_live_reservation_pay_desc, it.title), - confirmButtonTitle = activity.getString(R.string.screen_live_reservation_confirm), - confirmButtonClick = { processLiveReservation(roomId) }, - cancelButtonTitle = activity.getString(R.string.cancel), - cancelButtonClick = {} - ).show(screenWidthProvider()) - } - } - } - - private fun processLiveReservation(roomId: Long, password: String? = null) { - liveViewModel.reservationRoom(roomId, password) { - refreshHome() - activity.startActivity( - Intent(activity, LiveReservationCompleteActivity::class.java).apply { - putExtra(Constants.EXTRA_LIVE_RESERVATION_RESPONSE, it) - } - ) - } - } - - private fun modifyLive(roomDetail: GetRoomDetailResponse) { - activity.startActivity( - Intent(activity, LiveRoomEditActivity::class.java).apply { - putExtra(Constants.EXTRA_ROOM_DETAIL, roomDetail) - } - ) - } - - private fun startLive(roomId: Long) { - liveViewModel.startLive(roomId) { - refreshHome() - activity.runOnUiThread { openLiveRoom(roomId) } - } - } - - private fun cancelLive(roomId: Long) { - LiveCancelDialog( - activity = activity, - layoutInflater = layoutInflater, - title = activity.getString(R.string.screen_live_cancel_title), - hint = activity.getString(R.string.screen_live_cancel_hint), - confirmButtonTitle = activity.getString(R.string.screen_live_cancel_confirm), - confirmButtonClick = { reason -> - liveViewModel.cancelLive(roomId, reason) { - Toast.makeText( - activity, - activity.getString(R.string.screen_live_cancel_success), - Toast.LENGTH_LONG - ).show() - refreshHome() - } - }, - cancelButtonTitle = activity.getString(R.string.dialog_close), - cancelButtonClick = {} - ).show(screenWidthProvider()) - } - - private fun showPaidLiveEntryDialog( - roomId: Long, - beginDateTimeUtc: String, - price: Int, - isPrivateRoom: Boolean, - onEnterRoomSuccess: () -> Unit - ) { - val locale = Locale(LanguageManager.getEffectiveLanguage(activity)) - val wrappedContext = LocaleHelper.wrap(activity) - val beginDate = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).apply { - timeZone = TimeZone.getTimeZone("UTC") - }.parse(beginDateTimeUtc) ?: return - val now = Date() - val dateFormat = SimpleDateFormat("yyyy-MM-dd, HH:mm", locale) - val diffTime = now.time - beginDate.time - val hours = (diffTime / (1000 * 60 * 60)).toInt() - val mins = (diffTime / (1000 * 60)).toInt() % 60 - - if (isPrivateRoom) { - LiveRoomPasswordDialog( - activity = activity, - layoutInflater = layoutInflater, - can = price, - confirmButtonClick = { password -> - liveViewModel.enterRoom( - roomId = roomId, - onSuccess = onEnterRoomSuccess, - password = password - ) - } - ).show(screenWidthProvider()) - return - } - - LivePaymentDialog( - activity = activity, - layoutInflater = layoutInflater, - title = wrappedContext.getString(R.string.live_paid_title), - startDateTime = if (hours >= 1) dateFormat.format(beginDate) else null, - nowDateTime = if (hours >= 1) dateFormat.format(now) else null, - desc = wrappedContext.getString(R.string.live_paid_desc, price), - desc2 = if (hours >= 1) { - wrappedContext.getString(R.string.live_paid_warning, hours, mins) - } else { - null - }, - confirmButtonTitle = wrappedContext.getString(R.string.live_paid_confirm), - confirmButtonClick = { liveViewModel.enterRoom(roomId, onEnterRoomSuccess) }, - cancelButtonTitle = wrappedContext.getString(R.string.cancel), - cancelButtonClick = {} - ).show(screenWidthProvider()) - } - - private fun openLiveRoom(roomId: Long) { - activity.startActivity( - Intent(activity, LiveRoomActivity::class.java).apply { - putExtra(Constants.EXTRA_ROOM_ID, roomId) - } - ) - } -} 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 4b28320f..21ff563b 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 @@ -1403,29 +1403,30 @@ class CreatorChannelActivitySourceTest { } @Test - fun `owner FAB 라이브 생성 결과는 기존 enterLiveRoom 플로우로 입장한다`() { + fun `owner FAB 라이브 생성 결과는 명시적 결과 handler로 입장한다`() { val source = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" ).readText() val coordinator = projectFile( - "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt" + "app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt" ).readText() assertTrue(source.contains("ActivityResultContracts.StartActivityForResult()")) assertTrue(source.contains("private val liveViewModel: LiveViewModel by inject()")) - assertTrue(source.contains("private val liveCoordinator: CreatorChannelLiveCoordinator by lazy")) + assertTrue(source.contains("private val liveActionCoordinator: LiveActionCoordinator by lazy")) assertTrue(source.contains("private val liveRoomCreateLauncher")) assertTrue(source.contains("result.resultCode == RESULT_OK")) assertTrue(source.contains("result.data?.getLongExtra(Constants.EXTRA_ROOM_ID, 0L)")) assertTrue(source.contains("result.data?.getStringExtra(Constants.EXTRA_ROOM_CHANNEL_NAME)")) - assertTrue(source.contains("if (channelName != null)")) - assertTrue(source.contains("?.takeIf { it > 0L }")) - assertTrue(source.contains("?.let(liveCoordinator::enterLiveRoom)")) + assertTrue(source.contains("resolveLiveCreationResult(")) + assertTrue(source.contains("private fun handleLiveCreationResult(result: LiveCreationResult)")) + assertTrue(source.contains("is LiveCreationResult.Enter ->")) + assertTrue(source.contains("liveActionCoordinator.enterLiveRoom(result.roomId)")) assertTrue(source.contains("showToast(getString(R.string.creator_channel_live_created_message))")) assertTrue(source.contains("homeActionDelegate?.refreshHome()")) - assertTrue(coordinator.contains("fun enterLiveRoom(roomId: Long)")) - assertTrue(coordinator.contains("liveViewModel.getRoomDetail(roomId)")) - assertTrue(coordinator.contains("it.manager.id == SharedPreferenceManager.userId")) + assertTrue(coordinator.contains("fun enterLiveRoom(")) + assertTrue(coordinator.contains("liveViewModel.getRoomDetail(roomId, handleRoomDetail)")) + assertTrue(coordinator.contains("LiveEntryPolicy.decide(")) assertTrue(coordinator.contains("liveViewModel.enterRoom(roomId, onEnterRoomSuccess)")) assertTrue(coordinator.contains("LiveRoomPasswordDialog(")) assertTrue(coordinator.contains("LivePaymentDialog(")) @@ -1486,14 +1487,14 @@ class CreatorChannelActivitySourceTest { "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" ).readText() val coordinator = projectFile( - "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt" + "app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt" ).readText() - assertTrue(coordinator.contains("class CreatorChannelLiveCoordinator(")) + assertTrue(coordinator.contains("class LiveActionCoordinator(")) assertTrue(coordinator.contains("private val liveViewModel: LiveViewModel")) assertTrue(coordinator.contains("private val refreshHome: () -> Unit")) - assertTrue(activity.contains("liveCoordinator.enterLiveRoom(live.liveId)")) - assertTrue(activity.contains("CreatorActivityType.Live -> liveCoordinator.showLiveRoomDetail(schedule.targetId)")) + assertTrue(activity.contains("liveActionCoordinator.enterLiveRoom(live.liveId")) + assertTrue(activity.contains("CreatorActivityType.Live -> liveActionCoordinator.showLiveRoomDetail(schedule.targetId)")) assertFalse(activity.contains("private fun enterLiveRoom(roomId: Long)")) assertFalse(activity.contains("private fun showPaidLiveEntryDialog(")) assertFalse(activity.contains("private fun openLiveRoom(roomId: Long)")) @@ -1535,10 +1536,12 @@ 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("ensureV2Access(AccessRequirement.forAdultContent(live.isAdult))")) - assertTrue(activity.contains("liveCoordinator.enterLiveRoom(live.liveId)")) + assertTrue( + activity.contains( + "liveActionCoordinator.enterLiveRoom(live.liveId, requiresAdultContentAccess = live.isAdult)" + ) + ) 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(")) @@ -1589,13 +1592,13 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("ContentActionCommand.AudioDetail(")) assertFalse(source.contains("AudioContentDetailActivity")) assertTrue(source.contains("CreatorActivityType.Live")) - assertTrue(source.contains("CreatorActivityType.Live -> liveCoordinator.showLiveRoomDetail(schedule.targetId)")) + assertTrue(source.contains("CreatorActivityType.Live -> liveActionCoordinator.showLiveRoomDetail(schedule.targetId)")) } @Test fun `일정 라이브 상세는 예약 수정 시작 취소 액션을 연결한다`() { val coordinator = projectFile( - "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt" + "app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt" ).readText() assertTrue(coordinator.contains("fun showLiveRoomDetail(roomId: Long)"))