fix(content): 국가별 성인 콘텐츠 접근 동기화를 정리한다
This commit is contained in:
@@ -3,9 +3,13 @@ package kr.co.vividnext.sodalive.settings
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityContentSettingsBinding
|
||||
import kr.co.vividnext.sodalive.splash.SplashActivity
|
||||
@@ -16,37 +20,74 @@ class ContentSettingsActivity : BaseActivity<ActivityContentSettingsBinding>(
|
||||
) {
|
||||
|
||||
private val viewModel: ContentSettingsViewModel by inject()
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private val sensitiveContentConfirmDialog: SodaDialog by lazy {
|
||||
SodaDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = getString(R.string.dialog_sensitive_content_enable_title),
|
||||
desc = getString(R.string.dialog_sensitive_content_enable_message),
|
||||
confirmButtonTitle = getString(R.string.screen_live_room_yes),
|
||||
confirmButtonClick = { viewModel.toggleAdultContentVisible() },
|
||||
cancelButtonTitle = getString(R.string.screen_live_room_no),
|
||||
cancelButtonClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
bindData()
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
handleFinish()
|
||||
onBackPressedDispatcher.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
handleFinish()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
override fun setupView() {
|
||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||
|
||||
binding.toolbar.tvBack.text = getString(R.string.screen_content_settings_title)
|
||||
binding.toolbar.tvBack.setOnClickListener { handleFinish() }
|
||||
|
||||
if (intent.getBooleanExtra(Constants.EXTRA_SHOW_SENSITIVE_CONTENT_GUIDE, false)) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
getString(R.string.screen_content_settings_sensitive_content_guide),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
val isKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } == "KR"
|
||||
val canControlSensitiveContent = if (isKoreanCountry) {
|
||||
SharedPreferenceManager.isAuth
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
// 본인 인증 체크
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
if (canControlSensitiveContent) {
|
||||
binding.llAdultContentVisible.visibility = View.VISIBLE
|
||||
|
||||
// 19금 콘텐츠 보기 체크
|
||||
if (SharedPreferenceManager.isAdultContentVisible) {
|
||||
binding.llAdultContentPreference.visibility = View.VISIBLE
|
||||
|
||||
} else {
|
||||
binding.llAdultContentPreference.visibility = View.GONE
|
||||
}
|
||||
|
||||
// 19금 콘텐츠 보기 스위치 액션
|
||||
binding.ivAdultContentVisible.setOnClickListener {
|
||||
viewModel.toggleAdultContentVisible()
|
||||
val isAdultContentVisible = viewModel.isAdultContentVisible.value == true
|
||||
if (isAdultContentVisible) {
|
||||
viewModel.toggleAdultContentVisible()
|
||||
} else {
|
||||
sensitiveContentConfirmDialog.show(screenWidth)
|
||||
}
|
||||
}
|
||||
|
||||
binding.tvContentAll.setOnClickListener {
|
||||
@@ -88,6 +129,21 @@ class ContentSettingsActivity : BaseActivity<ActivityContentSettingsBinding>(
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) {
|
||||
if (it) {
|
||||
loadingDialog.show(screenWidth)
|
||||
} else {
|
||||
loadingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.toastLiveData.observe(this) {
|
||||
val text = it?.message ?: it?.resId?.let { resId -> getString(resId) }
|
||||
text?.let { message ->
|
||||
Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFinish() {
|
||||
|
||||
@@ -1,39 +1,201 @@
|
||||
package kr.co.vividnext.sodalive.settings
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.common.ToastMessage
|
||||
import kr.co.vividnext.sodalive.user.UserRepository
|
||||
|
||||
class ContentSettingsViewModel : BaseViewModel() {
|
||||
private var _isAdultContentVisible = MutableLiveData(
|
||||
SharedPreferenceManager.isAdultContentVisible
|
||||
class ContentSettingsViewModel(
|
||||
private val userRepository: UserRepository
|
||||
) : BaseViewModel() {
|
||||
private data class PreferenceState(
|
||||
val isAdultContentVisible: Boolean,
|
||||
val contentType: ContentType
|
||||
)
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val initialState = getCurrentPreferenceState()
|
||||
private var confirmedState = initialState
|
||||
private var isRequestInFlight = false
|
||||
private var pendingState: PreferenceState? = null
|
||||
private var syncRunnable: Runnable? = null
|
||||
|
||||
private val _isAdultContentVisible = MutableLiveData(initialState.isAdultContentVisible)
|
||||
val isAdultContentVisible: LiveData<Boolean>
|
||||
get() = _isAdultContentVisible
|
||||
|
||||
private var _adultContentPreference = MutableLiveData(
|
||||
ContentType.values()[SharedPreferenceManager.contentPreference]
|
||||
)
|
||||
private val _adultContentPreference = MutableLiveData(initialState.contentType)
|
||||
val adultContentPreference: LiveData<ContentType>
|
||||
get() = _adultContentPreference
|
||||
|
||||
private val _isLoading = MutableLiveData(false)
|
||||
val isLoading: LiveData<Boolean>
|
||||
get() = _isLoading
|
||||
|
||||
private val _toastLiveData = MutableLiveData<ToastMessage?>()
|
||||
val toastLiveData: LiveData<ToastMessage?>
|
||||
get() = _toastLiveData
|
||||
|
||||
var isChangedAdultContentVisible = false
|
||||
private set
|
||||
|
||||
fun toggleAdultContentVisible() {
|
||||
val adultContentVisible = SharedPreferenceManager.isAdultContentVisible
|
||||
_isAdultContentVisible.value = !adultContentVisible
|
||||
SharedPreferenceManager.isAdultContentVisible = !adultContentVisible
|
||||
isChangedAdultContentVisible = true
|
||||
val currentState = getCurrentPreferenceState()
|
||||
val nextState = PreferenceState(
|
||||
isAdultContentVisible = !currentState.isAdultContentVisible,
|
||||
contentType = if (currentState.isAdultContentVisible) {
|
||||
ContentType.ALL
|
||||
} else {
|
||||
currentState.contentType
|
||||
}
|
||||
)
|
||||
|
||||
if (adultContentVisible) {
|
||||
SharedPreferenceManager.contentPreference = ContentType.ALL.ordinal
|
||||
}
|
||||
applyLocalState(nextState)
|
||||
queueLatestStateForSync()
|
||||
}
|
||||
|
||||
fun setAdultContentPreference(adultContentPreference: ContentType) {
|
||||
_adultContentPreference.value = adultContentPreference
|
||||
SharedPreferenceManager.contentPreference = adultContentPreference.ordinal
|
||||
isChangedAdultContentVisible = true
|
||||
val currentState = getCurrentPreferenceState()
|
||||
if (currentState.contentType == adultContentPreference) {
|
||||
return
|
||||
}
|
||||
|
||||
applyLocalState(
|
||||
currentState.copy(contentType = adultContentPreference)
|
||||
)
|
||||
queueLatestStateForSync()
|
||||
}
|
||||
|
||||
private fun queueLatestStateForSync() {
|
||||
pendingState = getCurrentPreferenceState()
|
||||
|
||||
syncRunnable?.let { handler.removeCallbacks(it) }
|
||||
syncRunnable = Runnable {
|
||||
flushPendingState()
|
||||
}.also {
|
||||
handler.postDelayed(it, CONTENT_PREFERENCE_DEBOUNCE_DELAY_MS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flushPendingState() {
|
||||
if (isRequestInFlight) {
|
||||
return
|
||||
}
|
||||
|
||||
val targetState = pendingState ?: return
|
||||
if (targetState == confirmedState) {
|
||||
pendingState = null
|
||||
return
|
||||
}
|
||||
|
||||
pendingState = null
|
||||
syncContentPreference(targetState)
|
||||
}
|
||||
|
||||
private fun syncContentPreference(targetState: PreferenceState) {
|
||||
val request = UpdateContentPreferenceRequest(
|
||||
isAdultContentVisible = if (targetState.isAdultContentVisible != confirmedState.isAdultContentVisible) {
|
||||
targetState.isAdultContentVisible
|
||||
} else {
|
||||
null
|
||||
},
|
||||
contentType = if (targetState.contentType != confirmedState.contentType) {
|
||||
targetState.contentType
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
|
||||
if (request.isAdultContentVisible == null && request.contentType == null) {
|
||||
return
|
||||
}
|
||||
|
||||
isRequestInFlight = true
|
||||
_isLoading.value = true
|
||||
|
||||
compositeDisposable.add(
|
||||
userRepository.updateContentPreference(
|
||||
request = request,
|
||||
token = "Bearer ${SharedPreferenceManager.token}"
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ response ->
|
||||
isRequestInFlight = false
|
||||
_isLoading.value = false
|
||||
|
||||
if (response.success) {
|
||||
val syncedState = response.data?.let {
|
||||
PreferenceState(
|
||||
isAdultContentVisible = it.isAdultContentVisible,
|
||||
contentType = it.contentType
|
||||
)
|
||||
} ?: targetState
|
||||
|
||||
confirmedState = syncedState
|
||||
if (pendingState == null) {
|
||||
applyLocalState(syncedState)
|
||||
}
|
||||
flushPendingState()
|
||||
} else {
|
||||
rollbackToConfirmedState(response.message)
|
||||
}
|
||||
},
|
||||
{
|
||||
isRequestInFlight = false
|
||||
_isLoading.value = false
|
||||
it.message?.let { message -> Logger.e(message) }
|
||||
rollbackToConfirmedState(null)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun rollbackToConfirmedState(errorMessage: String?) {
|
||||
pendingState = null
|
||||
syncRunnable?.let { handler.removeCallbacks(it) }
|
||||
syncRunnable = null
|
||||
|
||||
applyLocalState(confirmedState)
|
||||
_toastLiveData.postValue(
|
||||
errorMessage?.let { ToastMessage(message = it) } ?: ToastMessage(resId = R.string.retry)
|
||||
)
|
||||
}
|
||||
|
||||
private fun applyLocalState(state: PreferenceState) {
|
||||
_isAdultContentVisible.value = state.isAdultContentVisible
|
||||
_adultContentPreference.value = state.contentType
|
||||
SharedPreferenceManager.isAdultContentVisible = state.isAdultContentVisible
|
||||
SharedPreferenceManager.contentPreference = state.contentType.ordinal
|
||||
isChangedAdultContentVisible = state != initialState
|
||||
}
|
||||
|
||||
private fun getCurrentPreferenceState(): PreferenceState {
|
||||
val localContentType = ContentType.entries.getOrNull(SharedPreferenceManager.contentPreference)
|
||||
?: ContentType.ALL
|
||||
|
||||
return PreferenceState(
|
||||
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||
contentType = localContentType
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
syncRunnable?.let { handler.removeCallbacks(it) }
|
||||
syncRunnable = null
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT_PREFERENCE_DEBOUNCE_DELAY_MS = 400L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ class SettingsActivity : BaseActivity<ActivitySettingsBinding>(ActivitySettingsB
|
||||
)
|
||||
}
|
||||
|
||||
if (SharedPreferenceManager.isAuth) {
|
||||
val isNotKoreanCountry = SharedPreferenceManager.countryCode.ifBlank { "KR" } != "KR"
|
||||
if (SharedPreferenceManager.isAuth || isNotKoreanCountry) {
|
||||
binding.dividerContentSettings.visibility = View.VISIBLE
|
||||
binding.rlContentSettings.visibility = View.VISIBLE
|
||||
binding.rlContentSettings.setOnClickListener {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.settings
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class UpdateContentPreferenceRequest(
|
||||
@SerializedName("isAdultContentVisible") val isAdultContentVisible: Boolean? = null,
|
||||
@SerializedName("contentType") val contentType: ContentType? = null
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.settings
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class UpdateContentPreferenceResponse(
|
||||
@SerializedName("isAdultContentVisible") val isAdultContentVisible: Boolean,
|
||||
@SerializedName("contentType") val contentType: ContentType
|
||||
)
|
||||
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.settings.notification
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kr.co.vividnext.sodalive.settings.ContentType
|
||||
|
||||
@Keep
|
||||
data class GetMemberInfoResponse(
|
||||
@@ -18,7 +19,13 @@ data class GetMemberInfoResponse(
|
||||
@SerializedName("followingChannelUploadContentNotice")
|
||||
val followingChannelUploadContentNotice: Boolean?,
|
||||
@SerializedName("auditionNotice")
|
||||
val auditionNotice: Boolean?
|
||||
val auditionNotice: Boolean?,
|
||||
@SerializedName("countryCode")
|
||||
val countryCode: String,
|
||||
@SerializedName("isAdultContentVisible")
|
||||
val isAdultContentVisible: Boolean,
|
||||
@SerializedName("contentType")
|
||||
val contentType: ContentType
|
||||
)
|
||||
|
||||
enum class MemberRole {
|
||||
@@ -26,5 +33,5 @@ enum class MemberRole {
|
||||
USER,
|
||||
|
||||
@SerializedName("CREATOR")
|
||||
CREATOR,
|
||||
CREATOR
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user