From d8221dc7841331a4a31b08d2cb6511a2a4103747 Mon Sep 17 00:00:00 2001 From: klaus Date: Mon, 20 Apr 2026 16:03:42 +0900 Subject: [PATCH] =?UTF-8?q?fix(live-room):=20=EB=AC=B4=EB=A3=8C=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=20=EB=9D=BC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=ED=8C=9D=EC=97=85=20=EB=85=B8=EC=B6=9C=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../sodalive/live/room/LiveRoomActivity.kt | 120 ++++++++++++++++++ .../live/room/LiveRoomDaroLightPopupPolicy.kt | 9 ++ .../sodalive/live/room/LiveRoomViewModel.kt | 22 ++++ .../room/LiveRoomDaroLightPopupPolicyTest.kt | 52 ++++++++ 4 files changed, 203 insertions(+) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicy.kt create mode 100644 app/src/test/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicyTest.kt diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt index f6ca7c1f..2be5ac9e 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt @@ -71,6 +71,15 @@ import com.bumptech.glide.request.target.Target import com.google.gson.Gson import com.orbitalsonic.waterwave.WaterWaveView import com.orhanobut.logger.Logger +import droom.daro.core.adunit.DaroLightPopupAdUnit +import droom.daro.core.listener.DaroLightPopupAdListener +import droom.daro.core.listener.DaroLightPopupAdLoaderListener +import droom.daro.core.model.DaroAdDisplayFailError +import droom.daro.core.model.DaroAdInfo +import droom.daro.core.model.DaroAdLoadError +import droom.daro.core.model.DaroLightPopupAd +import droom.daro.core.model.DaroLightPopupAdOptions +import droom.daro.loader.DaroLightPopupAdLoader import io.agora.rtc2.ClientRoleOptions import io.agora.rtc2.IRtcEngineEventHandler import io.agora.rtm.LinkStateEvent @@ -167,6 +176,11 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB private lateinit var roomInfoEditDialog: LiveRoomInfoEditDialog private lateinit var roomUserProfileDialog: LiveRoomUserProfileDialog + private var daroLightPopupAdLoader: DaroLightPopupAdLoader? = null + private var daroLightPopupAd: DaroLightPopupAd? = null + private var hasRequestedDaroLightPopupEligibility = false + private var hasAttemptedDaroLightPopup = false + private var isSpeakerMute = false private var isMicrophoneMute = false private var isSpeaker = false @@ -454,6 +468,8 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB override fun onDestroy() { // 액티비티 종료 전에 강제 음소거 상태를 원복한다. clearCapturePrivacyMuteState() + clearDaroLightPopupLoader() + clearDaroLightPopupAd() cropper.cleanup() hideKeyboard { viewModel.quitRoom(roomId) { @@ -1299,6 +1315,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB isCaptureRecordingAvailable = response.isCaptureRecordingAvailable syncRoomRoleState(response) + requestDaroLightPopupIfEligible() syncCaptureSecurityPolicy() binding.tvChatFreezeSwitch.visibility = if (isHost) { View.VISIBLE @@ -4275,9 +4292,112 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB ) ) } + + private fun requestDaroLightPopupIfEligible() { + if (hasRequestedDaroLightPopupEligibility || isHost) { + return + } + + hasRequestedDaroLightPopupEligibility = true + + if (DARO_LIGHT_POPUP_AD_UNIT_KEY.isBlank()) { + Logger.w("Daro light popup skipped because ad unit key is blank.") + return + } + + viewModel.getRoomPriceForDaroLightPopup(roomId = roomId) { roomPrice -> + val resolvedRoomPrice = roomPrice ?: return@getRoomPriceForDaroLightPopup + if ( + !shouldAttemptLiveRoomDaroLightPopup( + isHost = isHost, + roomPrice = resolvedRoomPrice, + hasAttemptedPopup = hasAttemptedDaroLightPopup + ) + ) { + return@getRoomPriceForDaroLightPopup + } + + hasAttemptedDaroLightPopup = true + loadDaroLightPopup() + } + } + + private fun loadDaroLightPopup() { + clearDaroLightPopupLoader() + clearDaroLightPopupAd() + + val adUnit = DaroLightPopupAdUnit( + key = DARO_LIGHT_POPUP_AD_UNIT_KEY, + placement = DARO_LIGHT_POPUP_PLACEMENT, + options = DaroLightPopupAdOptions() + ) + + daroLightPopupAdLoader = DaroLightPopupAdLoader( + context = this, + adUnit = adUnit + ).apply { + setListener(object : DaroLightPopupAdLoaderListener { + override fun onAdLoadSuccess(ad: DaroLightPopupAd, adInfo: DaroAdInfo) { + if (isFinishing || isDestroyed) { + ad.destroy() + clearDaroLightPopupLoader() + return + } + + daroLightPopupAd = ad + ad.setListener(object : DaroLightPopupAdListener { + override fun onAdImpression(adInfo: DaroAdInfo) = Unit + + override fun onAdClicked(adInfo: DaroAdInfo) = Unit + + override fun onShown(adInfo: DaroAdInfo) = Unit + + override fun onFailedToShow( + adInfo: DaroAdInfo, + error: DaroAdDisplayFailError + ) { + Logger.w( + "Daro light popup failed to show. message=${error.message}" + ) + clearDaroLightPopupLoader() + clearDaroLightPopupAd() + } + + override fun onDismiss(adInfo: DaroAdInfo) { + clearDaroLightPopupLoader() + clearDaroLightPopupAd() + } + }) + ad.show(this@LiveRoomActivity) + } + + override fun onAdLoadFail(err: DaroAdLoadError) { + Logger.w( + "Daro light popup load failed. code=${err.code}, message=${err.message}" + ) + clearDaroLightPopupLoader() + } + }) + load() + } + } + + private fun clearDaroLightPopupLoader() { + val adLoader = daroLightPopupAdLoader ?: return + daroLightPopupAdLoader = null + adLoader.destroy() + } + + private fun clearDaroLightPopupAd() { + val ad = daroLightPopupAd ?: return + daroLightPopupAd = null + ad.destroy() + } // endregion companion object { + private const val DARO_LIGHT_POPUP_AD_UNIT_KEY = "59082e9e-de1b-4f5d-bbc3-8b4124d110d8" + private const val DARO_LIGHT_POPUP_PLACEMENT = "LiveRoomFreeListener" private const val NO_CHATTING_TIME = 180L var isForeground: Boolean = false } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicy.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicy.kt new file mode 100644 index 00000000..c5537587 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicy.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.live.room + +internal fun shouldAttemptLiveRoomDaroLightPopup( + isHost: Boolean, + roomPrice: Int, + hasAttemptedPopup: Boolean +): Boolean { + return !isHost && roomPrice == 0 && !hasAttemptedPopup +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt index 1c790a86..f91e6dc5 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomViewModel.kt @@ -296,6 +296,28 @@ class LiveRoomViewModel( ) } + fun getRoomPriceForDaroLightPopup(roomId: Long, onResult: (Int?) -> Unit) { + compositeDisposable.add( + repository.getRoomDetail(roomId, token = "Bearer ${SharedPreferenceManager.token}") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + onResult(it.data.price) + } else { + Logger.w("Daro light popup room detail unavailable. roomId=$roomId") + onResult(null) + } + }, + { error -> + Logger.e(error.message ?: "Daro light popup room detail request failed.") + onResult(null) + } + ) + ) + } + fun isEqualToHostId(memberId: Int): Boolean { return memberId == roomInfoResponse.creatorId.toInt() } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicyTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicyTest.kt new file mode 100644 index 00000000..a4c70101 --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/live/room/LiveRoomDaroLightPopupPolicyTest.kt @@ -0,0 +1,52 @@ +package kr.co.vividnext.sodalive.live.room + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class LiveRoomDaroLightPopupPolicyTest { + + @Test + fun `비방장 무료 라이브에서 아직 시도하지 않았으면 라이트 팝업을 노출한다`() { + assertTrue( + shouldAttemptLiveRoomDaroLightPopup( + isHost = false, + roomPrice = 0, + hasAttemptedPopup = false + ) + ) + } + + @Test + fun `방장이면 무료 라이브여도 라이트 팝업을 노출하지 않는다`() { + assertFalse( + shouldAttemptLiveRoomDaroLightPopup( + isHost = true, + roomPrice = 0, + hasAttemptedPopup = false + ) + ) + } + + @Test + fun `유료 라이브면 비방장이어도 라이트 팝업을 노출하지 않는다`() { + assertFalse( + shouldAttemptLiveRoomDaroLightPopup( + isHost = false, + roomPrice = 100, + hasAttemptedPopup = false + ) + ) + } + + @Test + fun `이미 시도한 액티비티 인스턴스에서는 다시 노출하지 않는다`() { + assertFalse( + shouldAttemptLiveRoomDaroLightPopup( + isHost = false, + roomPrice = 0, + hasAttemptedPopup = true + ) + ) + } +}