fix(live-room): 무료 라이브 라이트 팝업 노출 조건을 추가한다

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-20 16:03:42 +09:00
parent 68e8941cc1
commit d8221dc784
4 changed files with 203 additions and 0 deletions

View File

@@ -71,6 +71,15 @@ import com.bumptech.glide.request.target.Target
import com.google.gson.Gson import com.google.gson.Gson
import com.orbitalsonic.waterwave.WaterWaveView import com.orbitalsonic.waterwave.WaterWaveView
import com.orhanobut.logger.Logger 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.ClientRoleOptions
import io.agora.rtc2.IRtcEngineEventHandler import io.agora.rtc2.IRtcEngineEventHandler
import io.agora.rtm.LinkStateEvent import io.agora.rtm.LinkStateEvent
@@ -167,6 +176,11 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private lateinit var roomInfoEditDialog: LiveRoomInfoEditDialog private lateinit var roomInfoEditDialog: LiveRoomInfoEditDialog
private lateinit var roomUserProfileDialog: LiveRoomUserProfileDialog 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 isSpeakerMute = false
private var isMicrophoneMute = false private var isMicrophoneMute = false
private var isSpeaker = false private var isSpeaker = false
@@ -454,6 +468,8 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
override fun onDestroy() { override fun onDestroy() {
// 액티비티 종료 전에 강제 음소거 상태를 원복한다. // 액티비티 종료 전에 강제 음소거 상태를 원복한다.
clearCapturePrivacyMuteState() clearCapturePrivacyMuteState()
clearDaroLightPopupLoader()
clearDaroLightPopupAd()
cropper.cleanup() cropper.cleanup()
hideKeyboard { hideKeyboard {
viewModel.quitRoom(roomId) { viewModel.quitRoom(roomId) {
@@ -1299,6 +1315,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
isCaptureRecordingAvailable = response.isCaptureRecordingAvailable isCaptureRecordingAvailable = response.isCaptureRecordingAvailable
syncRoomRoleState(response) syncRoomRoleState(response)
requestDaroLightPopupIfEligible()
syncCaptureSecurityPolicy() syncCaptureSecurityPolicy()
binding.tvChatFreezeSwitch.visibility = if (isHost) { binding.tvChatFreezeSwitch.visibility = if (isHost) {
View.VISIBLE View.VISIBLE
@@ -4275,9 +4292,112 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(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 // endregion
companion object { 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 private const val NO_CHATTING_TIME = 180L
var isForeground: Boolean = false var isForeground: Boolean = false
} }

View File

@@ -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
}

View File

@@ -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 { fun isEqualToHostId(memberId: Int): Boolean {
return memberId == roomInfoResponse.creatorId.toInt() return memberId == roomInfoResponse.creatorId.toInt()
} }

View File

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