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 + ) + ) + } +}