feat(creator-channel): FanTalk 상세 상태를 구현한다
This commit is contained in:
+244
@@ -0,0 +1,244 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.detail
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
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.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.common.ToastMessage
|
||||
import kr.co.vividnext.sodalive.common.UtcRelativeTimeTextFormatter
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelEvent
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelRepository
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.data.CreatorChannelFanTalkReplyResponse
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.model.toFanTalkReplyUiModel
|
||||
|
||||
class CreatorChannelFanTalkDetailViewModel(
|
||||
private val repository: CreatorChannelRepository,
|
||||
private val relativeTimeTextFormatter: UtcRelativeTimeTextFormatter
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _detailStateLiveData = MutableLiveData<CreatorChannelFanTalkDetailUiState>()
|
||||
val detailStateLiveData: LiveData<CreatorChannelFanTalkDetailUiState>
|
||||
get() = _detailStateLiveData
|
||||
|
||||
private val _replacementModalEventLiveData =
|
||||
MutableLiveData<CreatorChannelEvent<CreatorChannelFanTalkReplacementModalUiModel>>()
|
||||
val replacementModalEventLiveData: LiveData<CreatorChannelEvent<CreatorChannelFanTalkReplacementModalUiModel>>
|
||||
get() = _replacementModalEventLiveData
|
||||
|
||||
private val _replyChangedEventLiveData = MutableLiveData(false)
|
||||
val replyChangedEventLiveData: LiveData<Boolean>
|
||||
get() = _replyChangedEventLiveData
|
||||
|
||||
private val _toastLiveData = MutableLiveData<CreatorChannelEvent<ToastMessage>>()
|
||||
val toastLiveData: LiveData<CreatorChannelEvent<ToastMessage>>
|
||||
get() = _toastLiveData
|
||||
|
||||
private var creatorId: Long = 0L
|
||||
private var isSubmitting: Boolean = false
|
||||
private var pendingReplacementContent: String? = null
|
||||
|
||||
fun load(creatorId: Long, payload: CreatorChannelFanTalkDetailPayload) {
|
||||
this.creatorId = creatorId
|
||||
val parentFanTalk = payload.toUiModel()
|
||||
_detailStateLiveData.value = CreatorChannelFanTalkDetailUiState.Content(
|
||||
parentFanTalk = parentFanTalk,
|
||||
reply = parentFanTalk.reply
|
||||
)
|
||||
}
|
||||
|
||||
fun updateReplyInput(input: String) {
|
||||
val content = currentContent() ?: return
|
||||
_detailStateLiveData.value = content.copy(replyInput = input)
|
||||
}
|
||||
|
||||
fun submitReply() {
|
||||
val content = currentContent() ?: return
|
||||
val trimmedContent = content.replyInput.trim()
|
||||
if (trimmedContent.isEmpty() || isSubmitting) return
|
||||
|
||||
val editingTarget = content.editingTarget
|
||||
if (editingTarget is CreatorChannelFanTalkDetailEditTarget.Reply) {
|
||||
modifyReply(content, editingTarget.replyFanTalkId, trimmedContent)
|
||||
return
|
||||
}
|
||||
|
||||
val reply = content.reply
|
||||
if (reply == null) {
|
||||
writeReply(content, trimmedContent)
|
||||
} else {
|
||||
if (reply.fanTalkId <= 0L) {
|
||||
showFailureToast()
|
||||
return
|
||||
}
|
||||
pendingReplacementContent = trimmedContent
|
||||
_replacementModalEventLiveData.value = CreatorChannelEvent(CreatorChannelFanTalkReplacementModalUiModel())
|
||||
}
|
||||
}
|
||||
|
||||
fun confirmReplacement() {
|
||||
val content = currentContent() ?: return
|
||||
val reply = content.reply ?: return
|
||||
val replacementContent = pendingReplacementContent ?: content.replyInput.trim()
|
||||
if (replacementContent.isBlank() || isSubmitting) return
|
||||
if (reply.fanTalkId <= 0L) {
|
||||
pendingReplacementContent = null
|
||||
showFailureToast()
|
||||
return
|
||||
}
|
||||
|
||||
modifyReply(content, reply.fanTalkId, replacementContent)
|
||||
pendingReplacementContent = null
|
||||
}
|
||||
|
||||
fun cancelReplacement() {
|
||||
pendingReplacementContent = null
|
||||
}
|
||||
|
||||
fun consumeReplyChangedEvent() {
|
||||
_replyChangedEventLiveData.value = false
|
||||
}
|
||||
|
||||
fun cancelReplyEdit() {
|
||||
val content = currentContent() ?: return
|
||||
_detailStateLiveData.value = content.copy(replyInput = "", editingTarget = null)
|
||||
}
|
||||
|
||||
fun startReplyEdit() {
|
||||
val content = currentContent() ?: return
|
||||
val reply = content.reply ?: return
|
||||
_detailStateLiveData.value = content.copy(
|
||||
replyInput = reply.content,
|
||||
editingTarget = CreatorChannelFanTalkDetailEditTarget.Reply(reply.fanTalkId)
|
||||
)
|
||||
}
|
||||
|
||||
fun deleteReply() {
|
||||
val content = currentContent() ?: return
|
||||
val reply = content.reply ?: return
|
||||
if (isSubmitting) return
|
||||
|
||||
submitMutation(content, repository.deleteFanTalkReply(reply.fanTalkId, authToken())) { current ->
|
||||
current.copy(reply = null, replyInput = "", editingTarget = null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeReply(content: CreatorChannelFanTalkDetailUiState.Content, replyContent: String) {
|
||||
if (creatorId <= 0L || content.parentFanTalk.fanTalkId <= 0L) return
|
||||
|
||||
isSubmitting = true
|
||||
_detailStateLiveData.value = content.copy(isSubmitting = true)
|
||||
compositeDisposable.add(
|
||||
repository.writeFanTalkReply(
|
||||
fanTalkId = content.parentFanTalk.fanTalkId,
|
||||
creatorId = creatorId,
|
||||
content = replyContent,
|
||||
token = authToken()
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ response -> handleWriteReplyResponse(response) },
|
||||
{ error -> handleMutationError(error) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun modifyReply(
|
||||
content: CreatorChannelFanTalkDetailUiState.Content,
|
||||
replyFanTalkId: Long,
|
||||
replyContent: String
|
||||
) {
|
||||
if (replyFanTalkId <= 0L) return
|
||||
|
||||
submitMutation(
|
||||
content = content,
|
||||
request = repository.modifyFanTalkReply(replyFanTalkId, replyContent, authToken())
|
||||
) { current ->
|
||||
current.copy(
|
||||
reply = current.reply?.copy(content = replyContent),
|
||||
replyInput = "",
|
||||
editingTarget = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun submitMutation(
|
||||
content: CreatorChannelFanTalkDetailUiState.Content,
|
||||
request: Single<ApiResponse<CreatorChannelFanTalkReplyResponse>>,
|
||||
onSuccess: (CreatorChannelFanTalkDetailUiState.Content) -> CreatorChannelFanTalkDetailUiState.Content
|
||||
) {
|
||||
isSubmitting = true
|
||||
_detailStateLiveData.value = content.copy(isSubmitting = true)
|
||||
compositeDisposable.add(
|
||||
request
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ response -> handleMutationResponse(response, onSuccess) },
|
||||
{ error -> handleMutationError(error) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleWriteReplyResponse(response: ApiResponse<CreatorChannelFanTalkReplyResponse>) {
|
||||
isSubmitting = false
|
||||
val current = currentContent() ?: return
|
||||
if (response.success) {
|
||||
_detailStateLiveData.value = current.copy(
|
||||
reply = response.data?.toFanTalkReplyUiModel(relativeTimeTextFormatter) ?: current.reply,
|
||||
replyInput = "",
|
||||
isSubmitting = false,
|
||||
editingTarget = null
|
||||
)
|
||||
_replyChangedEventLiveData.value = true
|
||||
} else {
|
||||
showFailureToast(response.message)
|
||||
_detailStateLiveData.value = current.copy(isSubmitting = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMutationError(error: Throwable) {
|
||||
isSubmitting = false
|
||||
error.message?.let { Logger.e(it) }
|
||||
val current = currentContent() ?: return
|
||||
showFailureToast()
|
||||
_detailStateLiveData.value = current.copy(isSubmitting = false)
|
||||
}
|
||||
|
||||
private fun handleMutationResponse(
|
||||
response: ApiResponse<CreatorChannelFanTalkReplyResponse>,
|
||||
onSuccess: (CreatorChannelFanTalkDetailUiState.Content) -> CreatorChannelFanTalkDetailUiState.Content
|
||||
) {
|
||||
isSubmitting = false
|
||||
val current = currentContent() ?: return
|
||||
if (response.success) {
|
||||
_detailStateLiveData.value = onSuccess(current).copy(isSubmitting = false)
|
||||
_replyChangedEventLiveData.value = true
|
||||
} else {
|
||||
showFailureToast(response.message)
|
||||
_detailStateLiveData.value = current.copy(isSubmitting = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFailureToast(message: String? = null) {
|
||||
_toastLiveData.value = CreatorChannelEvent(
|
||||
if (message.isNullOrBlank()) {
|
||||
ToastMessage(resId = R.string.common_error_unknown)
|
||||
} else {
|
||||
ToastMessage(message = message)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun currentContent(): CreatorChannelFanTalkDetailUiState.Content? {
|
||||
return _detailStateLiveData.value as? CreatorChannelFanTalkDetailUiState.Content
|
||||
}
|
||||
|
||||
private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}"
|
||||
}
|
||||
Reference in New Issue
Block a user