refactor(live): 공통 액션 코디네이터를 추가한다

This commit is contained in:
2026-07-15 21:27:23 +09:00
parent c223b3b8a4
commit d1509d4359

View File

@@ -0,0 +1,392 @@
package kr.co.vividnext.sodalive.v2.live.action
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 kr.co.vividnext.sodalive.v2.access.AccessRequirement
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
class LiveActionCoordinator(
private val activity: FragmentActivity,
private val layoutInflater: LayoutInflater,
private val fragmentManager: FragmentManager,
private val liveViewModel: LiveViewModel,
private val screenWidthProvider: () -> Int,
private val refreshHome: () -> Unit,
private val canHandleResult: () -> Boolean = { !activity.isFinishing && !activity.isDestroyed }
) {
fun showLiveRoomDetail(roomId: Long) {
activity.ensureV2Access(AccessRequirement.Login) {
liveViewModel.getRoomDetail(roomId) { roomDetail ->
if (!canHandleUiResult()) return@getRoomDetail
activity.ensureV2Access(AccessRequirement.forAdultContent(roomDetail.isAdult)) {
showLiveRoomDetailContent(roomId)
}
}
}
}
private fun showLiveRoomDetailContent(roomId: Long) {
if (!canShowLiveRoomDetail(
canHandleUiResult = ::canHandleUiResult,
existingDetailFragment = fragmentManager.findFragmentByTag(LIVE_ROOM_DETAIL_TAG)
)
) {
return
}
val detailFragment = LiveRoomDetailFragment(
roomId,
onClickParticipant = {},
onClickReservation = { reservationRoom(roomId) },
onClickModify = { roomDetailResponse -> modifyLive(roomDetailResponse) },
onClickStart = { startLive(roomId) },
onClickCancel = { cancelLive(roomId) }
)
detailFragment.showNow(fragmentManager, LIVE_ROOM_DETAIL_TAG)
}
fun enterLiveRoom(
roomId: Long,
requiresAdultContentAccess: Boolean? = null,
onUnavailable: (() -> Unit)? = null
) {
val handleRoomDetail: (GetRoomDetailResponse) -> Unit = handleRoomDetail@{ roomDetail ->
if (!canHandleUiResult()) return@handleRoomDetail
val decision = decideLiveEntry(roomDetail)
val requiresAdultAccess = requiresAdultContentAccess == true || roomDetail.isAdult
runLiveEntryDecisionFlow(
decision = decision,
onShowDetail = {
handleUnavailableLive(roomId, requiresAdultAccess, onUnavailable)
},
ensureAdultAccess = { onAllowed ->
activity.ensureV2Access(
AccessRequirement.forAdultContent(requiresAdultAccess),
onAllowed
)
},
onAllowedEntry = {
if (canHandleUiResult()) {
stopAudioPlayback()
val onEnterRoomSuccess = {
activity.runOnUiThread {
runLiveActionUiSideEffect(::canHandleUiResult) {
openLiveRoom(roomId)
}
}
}
handleLiveEntry(roomId, roomDetail, decision, onEnterRoomSuccess)
}
}
)
}
val loadRoomDetail = { liveViewModel.getRoomDetail(roomId, handleRoomDetail) }
activity.ensureV2Access(AccessRequirement.Login, loadRoomDetail)
}
private fun stopAudioPlayback() {
activity.startService(
Intent(activity, AudioContentPlayService::class.java).apply {
action = AudioContentPlayService.MusicAction.STOP.name
}
)
activity.startService(
Intent(activity, AudioContentPlayerService::class.java).apply {
action = "STOP_SERVICE"
}
)
}
private fun handleLiveEntry(
roomId: Long,
roomDetail: GetRoomDetailResponse,
decision: LiveEntryDecision,
onEnterRoomSuccess: () -> Unit
) {
if (!canHandleUiResult()) return
when (decision) {
LiveEntryDecision.ShowDetail -> Unit
LiveEntryDecision.Enter -> liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
is LiveEntryDecision.RequestPassword -> {
LiveRoomPasswordDialog(
activity = activity,
layoutInflater = layoutInflater,
can = decision.can,
confirmButtonClick = { password ->
liveViewModel.enterRoom(
roomId = roomId,
onSuccess = onEnterRoomSuccess,
password = password
)
}
).show(screenWidthProvider())
}
LiveEntryDecision.RequestPayment -> {
showPaidLiveEntryDialog(
roomId = roomId,
beginDateTimeUtc = roomDetail.beginDateTimeUtc,
price = roomDetail.price,
onEnterRoomSuccess = onEnterRoomSuccess
)
}
}
}
private fun decideLiveEntry(roomDetail: GetRoomDetailResponse): LiveEntryDecision {
return LiveEntryPolicy.decide(
LiveEntryContext(
channelName = roomDetail.channelName,
managerId = roomDetail.manager.id,
currentUserId = SharedPreferenceManager.userId,
price = roomDetail.price,
isPaid = roomDetail.isPaid,
isPrivateRoom = roomDetail.isPrivateRoom
)
)
}
private fun handleUnavailableLive(
roomId: Long,
requiresAdultAccess: Boolean,
onUnavailable: (() -> Unit)?
) {
if (onUnavailable != null) {
onUnavailable()
return
}
activity.ensureV2Access(AccessRequirement.forAdultContent(requiresAdultAccess)) {
showLiveRoomDetailContent(roomId)
}
}
private fun reservationRoom(roomId: Long) {
liveViewModel.getRoomDetail(roomId) {
if (!canHandleUiResult()) return@getRoomDetail
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) {
if (!canHandleUiResult()) return@reservationRoom
refreshHome()
activity.startActivity(
Intent(activity, LiveReservationCompleteActivity::class.java).apply {
putExtra(Constants.EXTRA_LIVE_RESERVATION_RESPONSE, it)
}
)
}
}
private fun modifyLive(roomDetail: GetRoomDetailResponse) {
if (!canHandleUiResult()) return
activity.startActivity(
Intent(activity, LiveRoomEditActivity::class.java).apply {
putExtra(Constants.EXTRA_ROOM_DETAIL, roomDetail)
}
)
}
private fun startLive(roomId: Long) {
liveViewModel.startLive(roomId) {
if (!canHandleUiResult()) return@startLive
refreshHome()
activity.runOnUiThread {
runLiveActionUiSideEffect(::canHandleUiResult) {
openLiveRoom(roomId)
}
}
}
}
private fun cancelLive(roomId: Long) {
if (!canHandleUiResult()) return
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) {
if (!canHandleUiResult()) return@cancelLive
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,
onEnterRoomSuccess: () -> Unit
) {
if (!canHandleUiResult()) return
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
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)
}
)
}
private fun canHandleUiResult(): Boolean {
return canHandleLiveActionUiResult(
isActivityFinishing = activity.isFinishing,
isActivityDestroyed = activity.isDestroyed,
isStateSaved = fragmentManager.isStateSaved,
canHandleResult = canHandleResult
)
}
private companion object {
const val LIVE_ROOM_DETAIL_TAG = "LiveRoomDetailFragment"
}
}
internal fun runLiveActionUiSideEffect(
canHandleResult: () -> Boolean,
sideEffect: () -> Unit
): Boolean {
if (!canHandleResult()) return false
sideEffect()
return true
}
internal fun runLiveEntryDecisionFlow(
decision: LiveEntryDecision,
onShowDetail: () -> Unit,
ensureAdultAccess: (onAllowed: () -> Unit) -> Unit,
onAllowedEntry: () -> Unit
) {
if (decision == LiveEntryDecision.ShowDetail) {
onShowDetail()
return
}
ensureAdultAccess(onAllowedEntry)
}
internal fun canHandleLiveActionUiResult(
isActivityFinishing: Boolean,
isActivityDestroyed: Boolean,
isStateSaved: Boolean,
canHandleResult: () -> Boolean
): Boolean {
return !isActivityFinishing && !isActivityDestroyed && !isStateSaved && canHandleResult()
}
internal fun canShowLiveRoomDetail(
canHandleUiResult: () -> Boolean,
existingDetailFragment: Any?
): Boolean {
return canHandleUiResult() && existingDetailFragment == null
}