feat: 라이브 30분 연속 청취시 트래킹 API 호출 기능 추가

This commit is contained in:
2025-05-17 16:57:12 +09:00
parent 1720173a16
commit 9260d271a7
8 changed files with 88 additions and 4 deletions

View File

@@ -35,8 +35,8 @@ android {
applicationId "kr.co.vividnext.sodalive" applicationId "kr.co.vividnext.sodalive"
minSdk 23 minSdk 23
targetSdk 34 targetSdk 34
versionCode 165 versionCode 167
versionName "1.36.0" versionName "1.37.0"
} }
buildTypes { buildTypes {

View File

@@ -237,3 +237,5 @@
-dontwarn org.bouncycastle.jsse.** -dontwarn org.bouncycastle.jsse.**
-dontwarn org.conscrypt.* -dontwarn org.conscrypt.*
-dontwarn org.openjsse.** -dontwarn org.openjsse.**
-keep interface kr.co.vividnext.sodalive.tracking.UserEventApi

View File

@@ -155,6 +155,8 @@ import kr.co.vividnext.sodalive.settings.terms.TermsRepository
import kr.co.vividnext.sodalive.settings.terms.TermsViewModel import kr.co.vividnext.sodalive.settings.terms.TermsViewModel
import kr.co.vividnext.sodalive.tracking.AdTrackingApi import kr.co.vividnext.sodalive.tracking.AdTrackingApi
import kr.co.vividnext.sodalive.tracking.AdTrackingRepository import kr.co.vividnext.sodalive.tracking.AdTrackingRepository
import kr.co.vividnext.sodalive.tracking.UserEventApi
import kr.co.vividnext.sodalive.tracking.UserEventRepository
import kr.co.vividnext.sodalive.user.UserApi import kr.co.vividnext.sodalive.user.UserApi
import kr.co.vividnext.sodalive.user.UserRepository import kr.co.vividnext.sodalive.user.UserRepository
import kr.co.vividnext.sodalive.user.UserViewModel import kr.co.vividnext.sodalive.user.UserViewModel
@@ -213,6 +215,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
single { ApiBuilder().build(get(), CanTempApi::class.java) } single { ApiBuilder().build(get(), CanTempApi::class.java) }
single { ApiBuilder().build(get(), AuthApi::class.java) } single { ApiBuilder().build(get(), AuthApi::class.java) }
single { ApiBuilder().build(get(), UserApi::class.java) } single { ApiBuilder().build(get(), UserApi::class.java) }
single { ApiBuilder().build(get(), UserEventApi::class.java) }
single { ApiBuilder().build(get(), MenuApi::class.java) } single { ApiBuilder().build(get(), MenuApi::class.java) }
single { ApiBuilder().build(get(), LiveApi::class.java) } single { ApiBuilder().build(get(), LiveApi::class.java) }
single { ApiBuilder().build(get(), TermsApi::class.java) } single { ApiBuilder().build(get(), TermsApi::class.java) }
@@ -251,7 +254,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { LiveRoomCreateViewModel(get()) } viewModel { LiveRoomCreateViewModel(get()) }
viewModel { LiveTagViewModel(get()) } viewModel { LiveTagViewModel(get()) }
viewModel { LiveRoomEditViewModel(get()) } viewModel { LiveRoomEditViewModel(get()) }
viewModel { LiveRoomViewModel(get(), get(), get(), get()) } viewModel { LiveRoomViewModel(get(), get(), get(), get(), get()) }
viewModel { LiveRoomDonationMessageViewModel(get()) } viewModel { LiveRoomDonationMessageViewModel(get()) }
viewModel { ExplorerViewModel(get()) } viewModel { ExplorerViewModel(get()) }
viewModel { UserProfileViewModel(get(), get(), get()) } viewModel { UserProfileViewModel(get(), get(), get()) }
@@ -369,6 +372,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { OriginalAudioDramaContentAllRepository(get()) } factory { OriginalAudioDramaContentAllRepository(get()) }
factory { AdTrackingRepository(get()) } factory { AdTrackingRepository(get()) }
factory { SearchRepository(get()) } factory { SearchRepository(get()) }
factory { UserEventRepository(get()) }
} }
private val moduleList = listOf( private val moduleList = listOf(

View File

@@ -58,6 +58,9 @@ import io.agora.rtm.RtmChannelMember
import io.agora.rtm.RtmClientListener import io.agora.rtm.RtmClientListener
import io.agora.rtm.RtmMessage import io.agora.rtm.RtmMessage
import io.agora.rtm.RtmMessageType import io.agora.rtm.RtmMessageType
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.agora.Agora import kr.co.vividnext.sodalive.agora.Agora
@@ -100,6 +103,7 @@ import kr.co.vividnext.sodalive.report.ReportType
import kr.co.vividnext.sodalive.report.UserReportDialog import kr.co.vividnext.sodalive.report.UserReportDialog
import kr.co.vividnext.sodalive.settings.notification.MemberRole import kr.co.vividnext.sodalive.settings.notification.MemberRole
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.random.Random import kotlin.random.Random
@@ -1888,6 +1892,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
startNoChatting() startNoChatting()
} }
setHeartButtonPosition() setHeartButtonPosition()
startPeriodicPlaybackValidation()
}, },
rtmChannelJoinFail = { rtmChannelJoinFail = {
agoraConnectFail() agoraConnectFail()
@@ -2303,6 +2308,24 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
} }
} }
private fun startPeriodicPlaybackValidation() {
compositeDisposable.add(
Observable.interval(0, 30, TimeUnit.MINUTES)
.flatMapSingle {
viewModel.trackLiveContinuousListen30()
.subscribeOn(Schedulers.io())
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ response ->
Logger.e("성공: $response")
}, { error ->
Logger.e("실패: ${error.message}")
}
)
)
}
companion object { companion object {
private const val NO_CHATTING_TIME = 180L private const val NO_CHATTING_TIME = 180L
} }

View File

@@ -6,10 +6,12 @@ import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson import com.google.gson.Gson
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.Utils import kr.co.vividnext.sodalive.common.Utils
import kr.co.vividnext.sodalive.live.LiveRepository import kr.co.vividnext.sodalive.live.LiveRepository
@@ -28,6 +30,8 @@ import kr.co.vividnext.sodalive.live.roulette.SpinRouletteRequest
import kr.co.vividnext.sodalive.report.ReportRepository import kr.co.vividnext.sodalive.report.ReportRepository
import kr.co.vividnext.sodalive.report.ReportRequest import kr.co.vividnext.sodalive.report.ReportRequest
import kr.co.vividnext.sodalive.report.ReportType import kr.co.vividnext.sodalive.report.ReportType
import kr.co.vividnext.sodalive.tracking.UserEventRepository
import kr.co.vividnext.sodalive.tracking.UserEventType
import kr.co.vividnext.sodalive.user.UserRepository import kr.co.vividnext.sodalive.user.UserRepository
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody import okhttp3.MultipartBody
@@ -40,7 +44,8 @@ class LiveRoomViewModel(
private val repository: LiveRepository, private val repository: LiveRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val reportRepository: ReportRepository, private val reportRepository: ReportRepository,
private val rouletteRepository: RouletteRepository private val rouletteRepository: RouletteRepository,
private val userEventRepository: UserEventRepository
) : BaseViewModel() { ) : BaseViewModel() {
private val _roomInfoLiveData = MutableLiveData<GetRoomInfoResponse>() private val _roomInfoLiveData = MutableLiveData<GetRoomInfoResponse>()
val roomInfoLiveData: LiveData<GetRoomInfoResponse> val roomInfoLiveData: LiveData<GetRoomInfoResponse>
@@ -1124,6 +1129,13 @@ class LiveRoomViewModel(
) )
} }
fun trackLiveContinuousListen30(): Single<ApiResponse<Any>> {
return userEventRepository.trackEvent(
eventType = UserEventType.LIVE_CONTINUOUS_LISTEN_30,
token = "Bearer ${SharedPreferenceManager.token}"
)
}
private fun calculatePercentages(options: List<RouletteItem>): List<RoulettePreviewItem> { private fun calculatePercentages(options: List<RouletteItem>): List<RoulettePreviewItem> {
val updatedOptions = options.map { option -> val updatedOptions = options.map { option ->
RoulettePreviewItem( RoulettePreviewItem(

View File

@@ -0,0 +1,15 @@
package kr.co.vividnext.sodalive.tracking
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.POST
interface UserEventApi {
@POST("/user-action")
fun trackEvent(
@Body request: UserEventRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
}

View File

@@ -0,0 +1,13 @@
package kr.co.vividnext.sodalive.tracking
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
class UserEventRepository(private val api: UserEventApi) {
fun trackEvent(eventType: UserEventType, token: String): Single<ApiResponse<Any>> {
return api.trackEvent(
request = UserEventRequest(actionType = eventType),
authHeader = token
)
}
}

View File

@@ -0,0 +1,15 @@
package kr.co.vividnext.sodalive.tracking
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class UserEventRequest(
@SerializedName("actionType") val actionType: UserEventType
)
@Keep
enum class UserEventType {
@SerializedName("LIVE_CONTINUOUS_LISTEN_30")
LIVE_CONTINUOUS_LISTEN_30
}