refactor(character-comment): 답글 리스트 MVVM 적용 및 ViewModel 추가
- CharacterCommentReplyViewModel 추가: 로딩/토스트/페이지네이션/CRUD 로직 이관 - AppDI Koin 모듈에 Reply ViewModel 등록 - CharacterCommentReplyFragment에서 Repository 직접 접근 제거 및 바인딩 로직 추가
This commit is contained in:
		@@ -13,8 +13,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import coil.load
 | 
			
		||||
import coil.transform.CircleCropTransformation
 | 
			
		||||
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.BaseFragment
 | 
			
		||||
import kr.co.vividnext.sodalive.common.LoadingDialog
 | 
			
		||||
@@ -33,17 +31,15 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
			
		||||
    FragmentCharacterCommentReplyBinding::inflate
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    private val viewModel: CharacterCommentReplyViewModel by inject()
 | 
			
		||||
 | 
			
		||||
    private lateinit var imm: InputMethodManager
 | 
			
		||||
    private lateinit var loadingDialog: LoadingDialog
 | 
			
		||||
    private lateinit var adapter: CharacterCommentReplyAdapter
 | 
			
		||||
    private val repository: CharacterCommentRepository by inject()
 | 
			
		||||
 | 
			
		||||
    private var original: CharacterCommentResponse? = null
 | 
			
		||||
    private var characterId: Long = 0
 | 
			
		||||
 | 
			
		||||
    private var cursor: Long? = null
 | 
			
		||||
    private var isLoading: Boolean = false
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(
 | 
			
		||||
        inflater: LayoutInflater,
 | 
			
		||||
        container: ViewGroup?,
 | 
			
		||||
@@ -76,7 +72,9 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
			
		||||
        imm = requireContext().getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager
 | 
			
		||||
 | 
			
		||||
        setupView()
 | 
			
		||||
        loadMore() // 초기 로드 (스텁)
 | 
			
		||||
        bindData()
 | 
			
		||||
        viewModel.init(original!!)
 | 
			
		||||
        viewModel.loadReplies(characterId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupView() {
 | 
			
		||||
@@ -94,49 +92,8 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
			
		||||
            hideKeyboard()
 | 
			
		||||
            val text = binding.etComment.text.toString()
 | 
			
		||||
            if (text.isBlank()) return@setOnClickListener
 | 
			
		||||
            val originalId = original?.commentId ?: return@setOnClickListener
 | 
			
		||||
            val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
            loadingDialog.show(screenWidth)
 | 
			
		||||
            val d = repository.createReply(
 | 
			
		||||
                characterId = characterId,
 | 
			
		||||
                commentId = originalId,
 | 
			
		||||
                comment = text,
 | 
			
		||||
                token = token
 | 
			
		||||
            )
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .doFinally { loadingDialog.dismiss() }
 | 
			
		||||
                .subscribe({ resp ->
 | 
			
		||||
                    if (resp.success) {
 | 
			
		||||
                        // 성공 시 낙관적 UI 반영: 기존 스텁과 동일하게 즉시 목록에 추가
 | 
			
		||||
                        val me = CharacterReplyResponse(
 | 
			
		||||
                            replyId = System.currentTimeMillis(),
 | 
			
		||||
                            memberId = SharedPreferenceManager.userId,
 | 
			
		||||
                            memberProfileImage = SharedPreferenceManager.profileImage,
 | 
			
		||||
                            memberNickname = SharedPreferenceManager.nickname,
 | 
			
		||||
                            createdAt = System.currentTimeMillis(),
 | 
			
		||||
                            comment = text
 | 
			
		||||
                        )
 | 
			
		||||
                        val insertAt = adapter.items.size
 | 
			
		||||
                        adapter.items.add(me)
 | 
			
		||||
                        adapter.notifyItemInserted(insertAt)
 | 
			
		||||
                        binding.rvCommentReply.scrollToPosition(adapter.items.size - 1)
 | 
			
		||||
                        binding.etComment.setText("")
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Toast.makeText(
 | 
			
		||||
                            requireContext(),
 | 
			
		||||
                            resp.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                            Toast.LENGTH_SHORT
 | 
			
		||||
                        ).show()
 | 
			
		||||
                    }
 | 
			
		||||
                }, { e ->
 | 
			
		||||
                    Toast.makeText(
 | 
			
		||||
                        requireContext(),
 | 
			
		||||
                        e.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                        Toast.LENGTH_SHORT
 | 
			
		||||
                    ).show()
 | 
			
		||||
                })
 | 
			
		||||
            compositeDisposable.add(d)
 | 
			
		||||
            viewModel.createReply(characterId, text)
 | 
			
		||||
            binding.etComment.setText("")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        adapter = CharacterCommentReplyAdapter(
 | 
			
		||||
@@ -146,36 +103,7 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
			
		||||
                    onReport = {
 | 
			
		||||
                        val reportSheet = CharacterCommentReportBottomSheet.newInstance()
 | 
			
		||||
                        reportSheet.onSubmit = { reason ->
 | 
			
		||||
                            val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
                            val d = repository.reportComment(
 | 
			
		||||
                                characterId = characterId,
 | 
			
		||||
                                commentId = reply.replyId,
 | 
			
		||||
                                reason = reason,
 | 
			
		||||
                                token = token
 | 
			
		||||
                            ).subscribeOn(Schedulers.io())
 | 
			
		||||
                                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                                .subscribe({ resp ->
 | 
			
		||||
                                    if (resp.success) {
 | 
			
		||||
                                        Toast.makeText(
 | 
			
		||||
                                            requireContext(),
 | 
			
		||||
                                            "신고가 접수되었습니다.",
 | 
			
		||||
                                            Toast.LENGTH_SHORT
 | 
			
		||||
                                        ).show()
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        Toast.makeText(
 | 
			
		||||
                                            requireContext(),
 | 
			
		||||
                                            resp.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                                            Toast.LENGTH_SHORT
 | 
			
		||||
                                        ).show()
 | 
			
		||||
                                    }
 | 
			
		||||
                                }, { e ->
 | 
			
		||||
                                    Toast.makeText(
 | 
			
		||||
                                        requireContext(),
 | 
			
		||||
                                        e.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                                        Toast.LENGTH_SHORT
 | 
			
		||||
                                    ).show()
 | 
			
		||||
                                })
 | 
			
		||||
                            compositeDisposable.add(d)
 | 
			
		||||
                            viewModel.reportReply(characterId, reply.replyId, reason)
 | 
			
		||||
                        }
 | 
			
		||||
                        reportSheet.show(parentFragmentManager, "reply_report")
 | 
			
		||||
                    }
 | 
			
		||||
@@ -184,41 +112,7 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
			
		||||
                            .setTitle(getString(R.string.confirm_delete_title))
 | 
			
		||||
                            .setMessage(getString(R.string.confirm_delete_message))
 | 
			
		||||
                            .setPositiveButton(getString(R.string.confirm)) { _, _ ->
 | 
			
		||||
                                val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
                                loadingDialog.show(screenWidth)
 | 
			
		||||
                                val d = repository.deleteComment(
 | 
			
		||||
                                    characterId = characterId,
 | 
			
		||||
                                    commentId = reply.replyId,
 | 
			
		||||
                                    token = token
 | 
			
		||||
                                ).subscribeOn(Schedulers.io())
 | 
			
		||||
                                    .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                                    .doFinally { loadingDialog.dismiss() }
 | 
			
		||||
                                    .subscribe({ resp ->
 | 
			
		||||
                                        if (resp.success) {
 | 
			
		||||
                                            val index = adapter.items
 | 
			
		||||
                                                .indexOfFirst {
 | 
			
		||||
                                                    it is CharacterReplyResponse
 | 
			
		||||
                                                        && it.replyId == reply.replyId
 | 
			
		||||
                                                }
 | 
			
		||||
                                            if (index > 0) {
 | 
			
		||||
                                                adapter.items.removeAt(index)
 | 
			
		||||
                                                adapter.notifyItemRemoved(index)
 | 
			
		||||
                                            }
 | 
			
		||||
                                        } else {
 | 
			
		||||
                                            Toast.makeText(
 | 
			
		||||
                                                requireActivity(),
 | 
			
		||||
                                                resp.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                                                Toast.LENGTH_SHORT
 | 
			
		||||
                                            ).show()
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }, { e ->
 | 
			
		||||
                                        Toast.makeText(
 | 
			
		||||
                                            requireActivity(),
 | 
			
		||||
                                            e.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                                            Toast.LENGTH_SHORT
 | 
			
		||||
                                        ).show()
 | 
			
		||||
                                    })
 | 
			
		||||
                                compositeDisposable.add(d)
 | 
			
		||||
                                viewModel.deleteReply(characterId, reply.replyId)
 | 
			
		||||
                            }
 | 
			
		||||
                            .setNegativeButton(getString(R.string.cancel), null)
 | 
			
		||||
                            .show()
 | 
			
		||||
@@ -265,10 +159,8 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
			
		||||
                val lm = recyclerView.layoutManager as LinearLayoutManager
 | 
			
		||||
                val last = lm.findLastCompletelyVisibleItemPosition()
 | 
			
		||||
                val total = (recyclerView.adapter?.itemCount ?: 1) - 1
 | 
			
		||||
                val onlyHeader = adapter.items.size <= 1
 | 
			
		||||
                val canLoadMore = onlyHeader || cursor != null
 | 
			
		||||
                if (canLoadMore && !recyclerView.canScrollVertically(1) && last == total) {
 | 
			
		||||
                    loadMore()
 | 
			
		||||
                if (!recyclerView.canScrollVertically(1) && last == total) {
 | 
			
		||||
                    viewModel.loadReplies(characterId)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
@@ -279,53 +171,28 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
			
		||||
        imm.hideSoftInputFromWindow(view?.windowToken, 0)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun loadMore() {
 | 
			
		||||
        val originalId = original?.commentId ?: return
 | 
			
		||||
        if (isLoading) return
 | 
			
		||||
        // 초기 로드(헤더만 있는 상태)는 허용. 그 외에는 cursor가 null이면 더 이상 조회하지 않음
 | 
			
		||||
        val onlyHeader = adapter.items.size <= 1
 | 
			
		||||
        if (!onlyHeader && cursor == null) return
 | 
			
		||||
 | 
			
		||||
        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
        isLoading = true
 | 
			
		||||
        val d = repository.listReplies(
 | 
			
		||||
            characterId = characterId,
 | 
			
		||||
            commentId = originalId,
 | 
			
		||||
            limit = 20,
 | 
			
		||||
            cursor = cursor,
 | 
			
		||||
            token = token
 | 
			
		||||
        )
 | 
			
		||||
            .subscribeOn(Schedulers.io())
 | 
			
		||||
            .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
            .doFinally { isLoading = false }
 | 
			
		||||
            .subscribe({ resp ->
 | 
			
		||||
                if (resp.success) {
 | 
			
		||||
                    val data = resp.data
 | 
			
		||||
                    // 서버에서 original 포함되지만, 이미 헤더에 추가되어 있으므로 replies만 사용
 | 
			
		||||
                    val replies = data?.replies ?: emptyList()
 | 
			
		||||
                    if (replies.isNotEmpty()) {
 | 
			
		||||
                        val start = adapter.items.size
 | 
			
		||||
                        adapter.items.addAll(replies)
 | 
			
		||||
                        adapter.notifyItemRangeInserted(start, replies.size)
 | 
			
		||||
                    }
 | 
			
		||||
                    cursor = data?.cursor
 | 
			
		||||
                } else {
 | 
			
		||||
                    Toast.makeText(
 | 
			
		||||
                        requireContext(),
 | 
			
		||||
                        resp.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                        Toast.LENGTH_SHORT
 | 
			
		||||
                    ).show()
 | 
			
		||||
                }
 | 
			
		||||
            }, { e ->
 | 
			
		||||
                Toast.makeText(
 | 
			
		||||
                    requireContext(),
 | 
			
		||||
                    e.message ?: "요청 중 오류가 발생했습니다",
 | 
			
		||||
                    Toast.LENGTH_SHORT
 | 
			
		||||
                ).show()
 | 
			
		||||
            })
 | 
			
		||||
        compositeDisposable.add(d)
 | 
			
		||||
    private fun bindData() {
 | 
			
		||||
        viewModel.isLoading.observe(viewLifecycleOwner) { loading ->
 | 
			
		||||
            if (loading) loadingDialog.show(screenWidth) else loadingDialog.dismiss()
 | 
			
		||||
        }
 | 
			
		||||
        viewModel.toastLiveData.observe(viewLifecycleOwner) { msg ->
 | 
			
		||||
            msg?.let { showToast(it) }
 | 
			
		||||
        }
 | 
			
		||||
        viewModel.replies.observe(viewLifecycleOwner) { list ->
 | 
			
		||||
            // 헤더(원본 댓글)는 index 0에 유지, 나머지를 교체
 | 
			
		||||
            val header = if (adapter.items.isNotEmpty()) adapter.items.first() else original
 | 
			
		||||
            adapter.items.clear()
 | 
			
		||||
            header?.let { adapter.items.add(it) }
 | 
			
		||||
            adapter.items.addAll(list)
 | 
			
		||||
            adapter.notifyDataSetChanged()
 | 
			
		||||
            // 스크롤을 하단으로 이동 (신규 추가 시 사용자에게 피드백)
 | 
			
		||||
            if (adapter.itemCount > 0) {
 | 
			
		||||
                binding.rvCommentReply.scrollToPosition(adapter.itemCount - 1)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val EXTRA_CHARACTER_ID = "extra_character_id"
 | 
			
		||||
        private const val EXTRA_ORIGINAL_COMMENT_ID = "extra_original_comment_id"
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,185 @@
 | 
			
		||||
package kr.co.vividnext.sodalive.chat.character.comment
 | 
			
		||||
 | 
			
		||||
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.base.BaseViewModel
 | 
			
		||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
 | 
			
		||||
 | 
			
		||||
class CharacterCommentReplyViewModel(
 | 
			
		||||
    private val repository: CharacterCommentRepository
 | 
			
		||||
) : BaseViewModel() {
 | 
			
		||||
 | 
			
		||||
    private val _toastLiveData = MutableLiveData<String?>()
 | 
			
		||||
    val toastLiveData: LiveData<String?> get() = _toastLiveData
 | 
			
		||||
 | 
			
		||||
    private val _isLoading = MutableLiveData(false)
 | 
			
		||||
    val isLoading: LiveData<Boolean> get() = _isLoading
 | 
			
		||||
 | 
			
		||||
    private val _original = MutableLiveData<CharacterCommentResponse?>()
 | 
			
		||||
    val original: LiveData<CharacterCommentResponse?> get() = _original
 | 
			
		||||
 | 
			
		||||
    private val _replies = MutableLiveData<List<CharacterReplyResponse>>(emptyList())
 | 
			
		||||
    val replies: LiveData<List<CharacterReplyResponse>> get() = _replies
 | 
			
		||||
 | 
			
		||||
    private var cursor: Long? = null
 | 
			
		||||
    private var page: Int = 1
 | 
			
		||||
 | 
			
		||||
    fun init(original: CharacterCommentResponse) {
 | 
			
		||||
        _original.value = original
 | 
			
		||||
        reset()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun reset() {
 | 
			
		||||
        cursor = null
 | 
			
		||||
        page = 1
 | 
			
		||||
        _replies.value = emptyList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun loadReplies(characterId: Long) {
 | 
			
		||||
        val originalId = _original.value?.commentId ?: return
 | 
			
		||||
        if (_isLoading.value == true) return
 | 
			
		||||
        val onlyHeader = (_replies.value?.isEmpty() ?: true)
 | 
			
		||||
        if (!onlyHeader && cursor == null) return
 | 
			
		||||
 | 
			
		||||
        _isLoading.value = true
 | 
			
		||||
        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            repository.listReplies(
 | 
			
		||||
                characterId = characterId,
 | 
			
		||||
                commentId = originalId,
 | 
			
		||||
                limit = 20,
 | 
			
		||||
                cursor = cursor,
 | 
			
		||||
                token = token
 | 
			
		||||
            )
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribe({ resp ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    if (resp.success && resp.data != null) {
 | 
			
		||||
                        val newReplies = resp.data.replies
 | 
			
		||||
                        val current = _replies.value ?: emptyList()
 | 
			
		||||
                        _replies.postValue(current + newReplies)
 | 
			
		||||
                        cursor = resp.data.cursor
 | 
			
		||||
                        page += 1
 | 
			
		||||
                    } else {
 | 
			
		||||
                        val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
 | 
			
		||||
                        _toastLiveData.postValue(message)
 | 
			
		||||
                    }
 | 
			
		||||
                }, { e ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    Logger.e(e, "Character replies load failed")
 | 
			
		||||
                    _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
 | 
			
		||||
                })
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createReply(characterId: Long, comment: String) {
 | 
			
		||||
        if (comment.isBlank()) {
 | 
			
		||||
            _toastLiveData.postValue("내용을 입력하세요")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        val originalId = _original.value?.commentId ?: return
 | 
			
		||||
        if (_isLoading.value == true) return
 | 
			
		||||
        _isLoading.value = true
 | 
			
		||||
 | 
			
		||||
        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            repository.createReply(
 | 
			
		||||
                characterId = characterId,
 | 
			
		||||
                commentId = originalId,
 | 
			
		||||
                comment = comment,
 | 
			
		||||
                token = token
 | 
			
		||||
            )
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribe({ resp ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    if (resp.success) {
 | 
			
		||||
                        // 낙관적 추가
 | 
			
		||||
                        val me = CharacterReplyResponse(
 | 
			
		||||
                            replyId = System.currentTimeMillis(),
 | 
			
		||||
                            memberId = SharedPreferenceManager.userId,
 | 
			
		||||
                            memberProfileImage = SharedPreferenceManager.profileImage,
 | 
			
		||||
                            memberNickname = SharedPreferenceManager.nickname,
 | 
			
		||||
                            createdAt = System.currentTimeMillis(),
 | 
			
		||||
                            comment = comment
 | 
			
		||||
                        )
 | 
			
		||||
                        val current = _replies.value ?: emptyList()
 | 
			
		||||
                        _replies.postValue(current + listOf(me))
 | 
			
		||||
                    } else {
 | 
			
		||||
                        val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
 | 
			
		||||
                        _toastLiveData.postValue(message)
 | 
			
		||||
                    }
 | 
			
		||||
                }, { e ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    Logger.e(e, "Character reply create failed")
 | 
			
		||||
                    _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
 | 
			
		||||
                })
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun deleteReply(characterId: Long, replyId: Long) {
 | 
			
		||||
        if (_isLoading.value == true) return
 | 
			
		||||
        _isLoading.value = true
 | 
			
		||||
        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            repository.deleteComment(
 | 
			
		||||
                characterId = characterId,
 | 
			
		||||
                commentId = replyId,
 | 
			
		||||
                token = token
 | 
			
		||||
            )
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribe({ resp ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    if (resp.success) {
 | 
			
		||||
                        val current = _replies.value ?: emptyList()
 | 
			
		||||
                        _replies.postValue(current.filterNot { it.replyId == replyId })
 | 
			
		||||
                    } else {
 | 
			
		||||
                        val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
 | 
			
		||||
                        _toastLiveData.postValue(message)
 | 
			
		||||
                    }
 | 
			
		||||
                }, { e ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    Logger.e(e, "Character reply delete failed")
 | 
			
		||||
                    _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
 | 
			
		||||
                })
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun reportReply(characterId: Long, replyId: Long, reason: String) {
 | 
			
		||||
        if (reason.isBlank()) {
 | 
			
		||||
            _toastLiveData.postValue("신고 사유를 입력하세요")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        if (_isLoading.value == true) return
 | 
			
		||||
        _isLoading.value = true
 | 
			
		||||
        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            repository.reportComment(
 | 
			
		||||
                characterId = characterId,
 | 
			
		||||
                commentId = replyId,
 | 
			
		||||
                reason = reason,
 | 
			
		||||
                token = token
 | 
			
		||||
            )
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribe({ resp ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    if (resp.success) {
 | 
			
		||||
                        _toastLiveData.postValue("신고가 접수되었습니다.")
 | 
			
		||||
                    } else {
 | 
			
		||||
                        val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
 | 
			
		||||
                        _toastLiveData.postValue(message)
 | 
			
		||||
                    }
 | 
			
		||||
                }, { e ->
 | 
			
		||||
                    _isLoading.value = false
 | 
			
		||||
                    Logger.e(e, "Character reply report failed")
 | 
			
		||||
                    _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
 | 
			
		||||
                })
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -360,6 +360,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
 | 
			
		||||
        viewModel { CharacterDetailViewModel(get()) }
 | 
			
		||||
        viewModel { TalkTabViewModel(get()) }
 | 
			
		||||
        viewModel { kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentListViewModel(get()) }
 | 
			
		||||
        viewModel { kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentReplyViewModel(get()) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val repositoryModule = module {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user