feat(character-comment): 캐릭터 댓글 리스트 등록/목록/신고 API 연동 및 DI 등록
fix(character-comment): 캐릭터 댓글 리스트 무한 스크롤에서 cursor null 시 추가 호출 방지 - CharacterCommentApi/Repository 추가 - AppDI에 API/Repository 등록 - CharacterCommentListFragment: 등록 버튼 클릭 시 API 호출로 전환, 커서 페이징 목록 로드 적용, 신고 API 연동 - 로딩/에러 처리 및 중복 로드 방지 플래그 추가 - 스크롤 리스너에 canLoadMore 조건 추가(초기 또는 cursor 존재 시에만 호출) - loadMore()에 종료 가드 추가(adapter 비어있지 않고 cursor null이면 반환) - 댓글 1개인 경우 동일 내용 반복 로딩 문제 해결
This commit is contained in:
		@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.chat.character.comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.reactivex.rxjava3.core.Single
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.common.ApiResponse
 | 
				
			||||||
 | 
					import retrofit2.http.Body
 | 
				
			||||||
 | 
					import retrofit2.http.DELETE
 | 
				
			||||||
 | 
					import retrofit2.http.GET
 | 
				
			||||||
 | 
					import retrofit2.http.Header
 | 
				
			||||||
 | 
					import retrofit2.http.POST
 | 
				
			||||||
 | 
					import retrofit2.http.Path
 | 
				
			||||||
 | 
					import retrofit2.http.Query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface CharacterCommentApi {
 | 
				
			||||||
 | 
					    @POST("/api/chat/character/{characterId}/comments")
 | 
				
			||||||
 | 
					    fun createComment(
 | 
				
			||||||
 | 
					        @Path("characterId") characterId: Long,
 | 
				
			||||||
 | 
					        @Body request: CreateCharacterCommentRequest,
 | 
				
			||||||
 | 
					        @Header("Authorization") authHeader: String
 | 
				
			||||||
 | 
					    ): Single<ApiResponse<Any>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @POST("/api/chat/character/{characterId}/comments/{commentId}/replies")
 | 
				
			||||||
 | 
					    fun createReply(
 | 
				
			||||||
 | 
					        @Path("characterId") characterId: Long,
 | 
				
			||||||
 | 
					        @Path("commentId") commentId: Long,
 | 
				
			||||||
 | 
					        @Body request: CreateCharacterCommentRequest,
 | 
				
			||||||
 | 
					        @Header("Authorization") authHeader: String
 | 
				
			||||||
 | 
					    ): Single<ApiResponse<Any>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GET("/api/chat/character/{characterId}/comments")
 | 
				
			||||||
 | 
					    fun listComments(
 | 
				
			||||||
 | 
					        @Path("characterId") characterId: Long,
 | 
				
			||||||
 | 
					        @Query("limit") limit: Int = 20,
 | 
				
			||||||
 | 
					        @Query("cursor") cursor: Long?,
 | 
				
			||||||
 | 
					        @Header("Authorization") authHeader: String
 | 
				
			||||||
 | 
					    ): Single<ApiResponse<CharacterCommentListResponse>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GET("/api/chat/character/{characterId}/comments/{commentId}/replies")
 | 
				
			||||||
 | 
					    fun listReplies(
 | 
				
			||||||
 | 
					        @Path("characterId") characterId: Long,
 | 
				
			||||||
 | 
					        @Path("commentId") commentId: Long,
 | 
				
			||||||
 | 
					        @Query("limit") limit: Int = 20,
 | 
				
			||||||
 | 
					        @Query("cursor") cursor: Long?,
 | 
				
			||||||
 | 
					        @Header("Authorization") authHeader: String
 | 
				
			||||||
 | 
					    ): Single<ApiResponse<CharacterCommentRepliesResponse>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @DELETE("/api/chat/character/{characterId}/comments/{commentId}")
 | 
				
			||||||
 | 
					    fun deleteComment(
 | 
				
			||||||
 | 
					        @Path("characterId") characterId: Long,
 | 
				
			||||||
 | 
					        @Path("commentId") commentId: Long,
 | 
				
			||||||
 | 
					        @Header("Authorization") authHeader: String
 | 
				
			||||||
 | 
					    ): Single<ApiResponse<Any>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @POST("/api/chat/character/{characterId}/comments/{commentId}/reports")
 | 
				
			||||||
 | 
					    fun reportComment(
 | 
				
			||||||
 | 
					        @Path("characterId") characterId: Long,
 | 
				
			||||||
 | 
					        @Path("commentId") commentId: Long,
 | 
				
			||||||
 | 
					        @Body request: ReportCharacterCommentRequest,
 | 
				
			||||||
 | 
					        @Header("Authorization") authHeader: String
 | 
				
			||||||
 | 
					    ): Single<ApiResponse<Any>>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -14,17 +14,22 @@ import androidx.recyclerview.widget.RecyclerView
 | 
				
			|||||||
import coil.load
 | 
					import coil.load
 | 
				
			||||||
import coil.transform.CircleCropTransformation
 | 
					import coil.transform.CircleCropTransformation
 | 
				
			||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 | 
					import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 | 
				
			||||||
 | 
					import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
 | 
				
			||||||
 | 
					import io.reactivex.rxjava3.schedulers.Schedulers
 | 
				
			||||||
import kr.co.vividnext.sodalive.R
 | 
					import kr.co.vividnext.sodalive.R
 | 
				
			||||||
import kr.co.vividnext.sodalive.base.BaseFragment
 | 
					import kr.co.vividnext.sodalive.base.BaseFragment
 | 
				
			||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
 | 
					import kr.co.vividnext.sodalive.common.LoadingDialog
 | 
				
			||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
 | 
					import kr.co.vividnext.sodalive.common.SharedPreferenceManager
 | 
				
			||||||
import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentListBinding
 | 
					import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentListBinding
 | 
				
			||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
 | 
					import kr.co.vividnext.sodalive.extensions.dpToPx
 | 
				
			||||||
 | 
					import org.koin.android.ext.android.inject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBinding>(
 | 
					class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBinding>(
 | 
				
			||||||
    FragmentCharacterCommentListBinding::inflate
 | 
					    FragmentCharacterCommentListBinding::inflate
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val repository: CharacterCommentRepository by inject()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var imm: InputMethodManager
 | 
					    private lateinit var imm: InputMethodManager
 | 
				
			||||||
    private lateinit var loadingDialog: LoadingDialog
 | 
					    private lateinit var loadingDialog: LoadingDialog
 | 
				
			||||||
    private lateinit var adapter: CharacterCommentsAdapter
 | 
					    private lateinit var adapter: CharacterCommentsAdapter
 | 
				
			||||||
@@ -48,8 +53,8 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        setupView()
 | 
					        setupView()
 | 
				
			||||||
        bindData()
 | 
					        bindData()
 | 
				
			||||||
        // 초기 로드 (스텁)
 | 
					        // 초기 로드
 | 
				
			||||||
        loadMore()
 | 
					        resetAndLoad()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun hideDialog() {
 | 
					    private fun hideDialog() {
 | 
				
			||||||
@@ -70,21 +75,31 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
 | 
				
			|||||||
            hideKeyboard()
 | 
					            hideKeyboard()
 | 
				
			||||||
            val comment = binding.etComment.text.toString()
 | 
					            val comment = binding.etComment.text.toString()
 | 
				
			||||||
            if (comment.isBlank()) return@setOnClickListener
 | 
					            if (comment.isBlank()) return@setOnClickListener
 | 
				
			||||||
            // 스텁: 로컬에 즉시 추가 (CharacterCommentDto 기반)
 | 
					            val token = "Bearer ${SharedPreferenceManager.token}"
 | 
				
			||||||
            val me = CharacterCommentResponse(
 | 
					            loadingDialog.show(screenWidth)
 | 
				
			||||||
                commentId = System.currentTimeMillis(),
 | 
					            val d = repository.createComment(characterId, comment, token)
 | 
				
			||||||
                memberId = SharedPreferenceManager.userId,
 | 
					                .subscribeOn(Schedulers.io())
 | 
				
			||||||
                memberProfileImage = SharedPreferenceManager.profileImage,
 | 
					                .observeOn(AndroidSchedulers.mainThread())
 | 
				
			||||||
                memberNickname = SharedPreferenceManager.nickname,
 | 
					                .doFinally { loadingDialog.dismiss() }
 | 
				
			||||||
                createdAt = System.currentTimeMillis(),
 | 
					                .subscribe({ resp ->
 | 
				
			||||||
                replyCount = 0,
 | 
					                    if (resp.success) {
 | 
				
			||||||
                comment = comment
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            adapter.items.add(me)
 | 
					 | 
				
			||||||
            adapter.notifyItemInserted(adapter.items.size - 1)
 | 
					 | 
				
			||||||
            binding.rvComment.scrollToPosition(adapter.items.size - 1)
 | 
					 | 
				
			||||||
                        binding.etComment.setText("")
 | 
					                        binding.etComment.setText("")
 | 
				
			||||||
            Toast.makeText(requireContext(), "등록되었습니다 (stub)", Toast.LENGTH_SHORT).show()
 | 
					                        resetAndLoad()
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        Toast.makeText(
 | 
				
			||||||
 | 
					                            requireContext(),
 | 
				
			||||||
 | 
					                            resp.message ?: "요청 중 오류가 발생했습니다",
 | 
				
			||||||
 | 
					                            Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                        ).show()
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }, { e ->
 | 
				
			||||||
 | 
					                    Toast.makeText(
 | 
				
			||||||
 | 
					                        requireContext(),
 | 
				
			||||||
 | 
					                        e.message ?: "요청 중 오류가 발생했습니다",
 | 
				
			||||||
 | 
					                        Toast.LENGTH_SHORT
 | 
				
			||||||
 | 
					                    ).show()
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            compositeDisposable.add(d)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        adapter = CharacterCommentsAdapter(
 | 
					        adapter = CharacterCommentsAdapter(
 | 
				
			||||||
@@ -92,11 +107,35 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
 | 
				
			|||||||
            onClickMore = { item, isOwner, anchor ->
 | 
					            onClickMore = { item, isOwner, anchor ->
 | 
				
			||||||
                CharacterCommentMoreBottomSheet.newInstance(isOwner).apply {
 | 
					                CharacterCommentMoreBottomSheet.newInstance(isOwner).apply {
 | 
				
			||||||
                    onReport = {
 | 
					                    onReport = {
 | 
				
			||||||
                        // 더보기 닫히고 신고 BottomSheet 열림
 | 
					 | 
				
			||||||
                        val reportSheet = CharacterCommentReportBottomSheet.newInstance()
 | 
					                        val reportSheet = CharacterCommentReportBottomSheet.newInstance()
 | 
				
			||||||
                        reportSheet.onSubmit = { reason ->
 | 
					                        reportSheet.onSubmit = { reason ->
 | 
				
			||||||
                            // 신고 API 스텁 호출 지점
 | 
					                            val token = "Bearer ${SharedPreferenceManager.token}"
 | 
				
			||||||
                            Toast.makeText(requireContext(), "신고 접수: $reason (stub)", Toast.LENGTH_SHORT).show()
 | 
					                            val d =
 | 
				
			||||||
 | 
					                                repository.reportComment(characterId, item.commentId, reason, 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)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        reportSheet.show(childFragmentManager, "comment_report")
 | 
					                        reportSheet.show(childFragmentManager, "comment_report")
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -107,7 +146,8 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
 | 
				
			|||||||
                            .setMessage(getString(R.string.confirm_delete_message))
 | 
					                            .setMessage(getString(R.string.confirm_delete_message))
 | 
				
			||||||
                            .setPositiveButton(getString(R.string.confirm)) { _, _ ->
 | 
					                            .setPositiveButton(getString(R.string.confirm)) { _, _ ->
 | 
				
			||||||
                                // 삭제 API 스텁 호출 지점
 | 
					                                // 삭제 API 스텁 호출 지점
 | 
				
			||||||
                                val index = adapter.items.indexOfFirst { it.commentId == item.commentId }
 | 
					                                val index =
 | 
				
			||||||
 | 
					                                    adapter.items.indexOfFirst { it.commentId == item.commentId }
 | 
				
			||||||
                                if (index >= 0) {
 | 
					                                if (index >= 0) {
 | 
				
			||||||
                                    adapter.items.removeAt(index)
 | 
					                                    adapter.items.removeAt(index)
 | 
				
			||||||
                                    adapter.notifyItemRemoved(index)
 | 
					                                    adapter.notifyItemRemoved(index)
 | 
				
			||||||
@@ -167,7 +207,9 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
 | 
				
			|||||||
                val lastVisible =
 | 
					                val lastVisible =
 | 
				
			||||||
                    (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
 | 
					                    (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
 | 
				
			||||||
                val total = recyclerView.adapter?.itemCount ?: 0
 | 
					                val total = recyclerView.adapter?.itemCount ?: 0
 | 
				
			||||||
                if (!recyclerView.canScrollVertically(1) && lastVisible == total - 1) {
 | 
					                // 초기 진입(아이템 없음) 또는 다음 페이지가 존재할 때(cursor != null)에만 로드
 | 
				
			||||||
 | 
					                val canLoadMore = adapter.items.isEmpty() || cursor != null
 | 
				
			||||||
 | 
					                if (canLoadMore && !recyclerView.canScrollVertically(1) && lastVisible == total - 1) {
 | 
				
			||||||
                    loadMore()
 | 
					                    loadMore()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -186,28 +228,47 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
 | 
				
			|||||||
        imm.hideSoftInputFromWindow(view?.windowToken, 0)
 | 
					        imm.hideSoftInputFromWindow(view?.windowToken, 0)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var page = 1
 | 
					    private var cursor: Long? = null
 | 
				
			||||||
    private fun loadMore() {
 | 
					    private var isLoading = false
 | 
				
			||||||
        // API 스텁: 더미 데이터 생성
 | 
					
 | 
				
			||||||
        val newItems = (1..10).map { idx ->
 | 
					    private fun resetAndLoad() {
 | 
				
			||||||
            val id = page * 1000L + idx
 | 
					        cursor = null
 | 
				
			||||||
            val writerId = if (idx % 5 == 0) SharedPreferenceManager.userId else -idx.toLong()
 | 
					        adapter.items.clear()
 | 
				
			||||||
            CharacterCommentResponse(
 | 
					        adapter.notifyDataSetChanged()
 | 
				
			||||||
                commentId = id,
 | 
					        loadMore()
 | 
				
			||||||
                memberId = writerId,
 | 
					 | 
				
			||||||
                memberProfileImage = "",
 | 
					 | 
				
			||||||
                memberNickname = "게스트$id",
 | 
					 | 
				
			||||||
                createdAt = System.currentTimeMillis() - (idx * 60_000L * page),
 | 
					 | 
				
			||||||
                replyCount = (idx % 3),
 | 
					 | 
				
			||||||
                comment = "캐릭터 댓글 예시 텍스트 $id"
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        if (page == 1) adapter.items.clear()
 | 
					
 | 
				
			||||||
 | 
					    private fun loadMore() {
 | 
				
			||||||
 | 
					        if (isLoading) return
 | 
				
			||||||
 | 
					        // 초기 로드(아이템 없음)는 허용. 그 외에는 cursor가 null이면 더 이상 로드하지 않음
 | 
				
			||||||
 | 
					        if (adapter.items.isNotEmpty() && cursor == null) return
 | 
				
			||||||
 | 
					        val token = "Bearer ${SharedPreferenceManager.token}"
 | 
				
			||||||
 | 
					        isLoading = true
 | 
				
			||||||
 | 
					        val d = repository.listComments(characterId, 20, cursor, token)
 | 
				
			||||||
 | 
					            .subscribeOn(Schedulers.io())
 | 
				
			||||||
 | 
					            .observeOn(AndroidSchedulers.mainThread())
 | 
				
			||||||
 | 
					            .doFinally { isLoading = false }
 | 
				
			||||||
 | 
					            .subscribe({ resp ->
 | 
				
			||||||
 | 
					                if (resp.success) {
 | 
				
			||||||
 | 
					                    val data = resp.data
 | 
				
			||||||
 | 
					                    val items = data?.comments ?: emptyList()
 | 
				
			||||||
                    val start = adapter.items.size
 | 
					                    val start = adapter.items.size
 | 
				
			||||||
        adapter.items.addAll(newItems)
 | 
					                    adapter.items.addAll(items)
 | 
				
			||||||
        adapter.notifyItemRangeInserted(start, newItems.size)
 | 
					                    adapter.notifyItemRangeInserted(start, items.size)
 | 
				
			||||||
                    binding.tvCommentCount.text = "${adapter.items.size}"
 | 
					                    binding.tvCommentCount.text = "${adapter.items.size}"
 | 
				
			||||||
        page++
 | 
					                    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)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,73 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.chat.character.comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CharacterCommentRepository(private val api: CharacterCommentApi) {
 | 
				
			||||||
 | 
					    fun createComment(
 | 
				
			||||||
 | 
					        characterId: Long,
 | 
				
			||||||
 | 
					        comment: String,
 | 
				
			||||||
 | 
					        token: String
 | 
				
			||||||
 | 
					    ) = api.createComment(
 | 
				
			||||||
 | 
					        characterId = characterId,
 | 
				
			||||||
 | 
					        request = CreateCharacterCommentRequest(comment = comment),
 | 
				
			||||||
 | 
					        authHeader = token
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun createReply(
 | 
				
			||||||
 | 
					        characterId: Long,
 | 
				
			||||||
 | 
					        commentId: Long,
 | 
				
			||||||
 | 
					        comment: String,
 | 
				
			||||||
 | 
					        token: String
 | 
				
			||||||
 | 
					    ) = api.createReply(
 | 
				
			||||||
 | 
					        characterId = characterId,
 | 
				
			||||||
 | 
					        commentId = commentId,
 | 
				
			||||||
 | 
					        request = CreateCharacterCommentRequest(comment = comment),
 | 
				
			||||||
 | 
					        authHeader = token
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun listComments(
 | 
				
			||||||
 | 
					        characterId: Long,
 | 
				
			||||||
 | 
					        limit: Int,
 | 
				
			||||||
 | 
					        cursor: Long?,
 | 
				
			||||||
 | 
					        token: String
 | 
				
			||||||
 | 
					    ) = api.listComments(
 | 
				
			||||||
 | 
					        characterId = characterId,
 | 
				
			||||||
 | 
					        limit = limit,
 | 
				
			||||||
 | 
					        cursor = cursor,
 | 
				
			||||||
 | 
					        authHeader = token
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun listReplies(
 | 
				
			||||||
 | 
					        characterId: Long,
 | 
				
			||||||
 | 
					        commentId: Long,
 | 
				
			||||||
 | 
					        limit: Int,
 | 
				
			||||||
 | 
					        cursor: Long?,
 | 
				
			||||||
 | 
					        token: String
 | 
				
			||||||
 | 
					    ) = api.listReplies(
 | 
				
			||||||
 | 
					        characterId = characterId,
 | 
				
			||||||
 | 
					        commentId = commentId,
 | 
				
			||||||
 | 
					        limit = limit,
 | 
				
			||||||
 | 
					        cursor = cursor,
 | 
				
			||||||
 | 
					        authHeader = token
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun deleteComment(
 | 
				
			||||||
 | 
					        characterId: Long,
 | 
				
			||||||
 | 
					        commentId: Long,
 | 
				
			||||||
 | 
					        token: String
 | 
				
			||||||
 | 
					    ) = api.deleteComment(
 | 
				
			||||||
 | 
					        characterId = characterId,
 | 
				
			||||||
 | 
					        commentId = commentId,
 | 
				
			||||||
 | 
					        authHeader = token
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun reportComment(
 | 
				
			||||||
 | 
					        characterId: Long,
 | 
				
			||||||
 | 
					        commentId: Long,
 | 
				
			||||||
 | 
					        reason: String,
 | 
				
			||||||
 | 
					        token: String
 | 
				
			||||||
 | 
					    ) = api.reportComment(
 | 
				
			||||||
 | 
					        characterId = characterId,
 | 
				
			||||||
 | 
					        commentId = commentId,
 | 
				
			||||||
 | 
					        request = ReportCharacterCommentRequest(content = reason),
 | 
				
			||||||
 | 
					        authHeader = token
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -20,11 +20,16 @@ import kr.co.vividnext.sodalive.common.SharedPreferenceManager
 | 
				
			|||||||
import kr.co.vividnext.sodalive.databinding.ActivityCharacterDetailBinding
 | 
					import kr.co.vividnext.sodalive.databinding.ActivityCharacterDetailBinding
 | 
				
			||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
 | 
					import kr.co.vividnext.sodalive.extensions.dpToPx
 | 
				
			||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
 | 
					import org.koin.androidx.viewmodel.ext.android.viewModel
 | 
				
			||||||
 | 
					import org.koin.android.ext.android.inject
 | 
				
			||||||
 | 
					import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
 | 
				
			||||||
 | 
					import io.reactivex.rxjava3.schedulers.Schedulers
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
 | 
					class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
 | 
				
			||||||
    ActivityCharacterDetailBinding::inflate
 | 
					    ActivityCharacterDetailBinding::inflate
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    private val viewModel: CharacterDetailViewModel by viewModel()
 | 
					    private val viewModel: CharacterDetailViewModel by viewModel()
 | 
				
			||||||
 | 
					    private val commentRepository: CharacterCommentRepository by inject()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var loadingDialog: LoadingDialog
 | 
					    private lateinit var loadingDialog: LoadingDialog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -283,6 +288,35 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                binding.ivSendComment.setOnClickListener {
 | 
					                binding.ivSendComment.setOnClickListener {
 | 
				
			||||||
 | 
					                    val text = binding.etCommentInput.text?.toString()?.trim().orEmpty()
 | 
				
			||||||
 | 
					                    if (text.isBlank()) return@setOnClickListener
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L
 | 
				
			||||||
 | 
					                    val idFromIntent = intent.getLongExtra(EXTRA_CHARACTER_ID, 0L)
 | 
				
			||||||
 | 
					                    val characterId = if (idFromState > 0) idFromState else idFromIntent
 | 
				
			||||||
 | 
					                    if (characterId <= 0) {
 | 
				
			||||||
 | 
					                        showToast("잘못된 접근 입니다.")
 | 
				
			||||||
 | 
					                        return@setOnClickListener
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    val token = "Bearer ${SharedPreferenceManager.token}"
 | 
				
			||||||
 | 
					                    loadingDialog.show(screenWidth)
 | 
				
			||||||
 | 
					                    val d = commentRepository.createComment(characterId, text, token)
 | 
				
			||||||
 | 
					                        .subscribeOn(Schedulers.io())
 | 
				
			||||||
 | 
					                        .observeOn(AndroidSchedulers.mainThread())
 | 
				
			||||||
 | 
					                        .doFinally { loadingDialog.dismiss() }
 | 
				
			||||||
 | 
					                        .subscribe({ resp ->
 | 
				
			||||||
 | 
					                            if (resp.success) {
 | 
				
			||||||
 | 
					                                binding.etCommentInput.setText("")
 | 
				
			||||||
 | 
					                                showToast("등록되었습니다.")
 | 
				
			||||||
 | 
					                                viewModel.load(characterId)
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                showToast(resp.message ?: "요청 중 오류가 발생했습니다")
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }, { e ->
 | 
				
			||||||
 | 
					                            showToast(e.message ?: "요청 중 오류가 발생했습니다")
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    compositeDisposable.add(d)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,11 +67,14 @@ import kr.co.vividnext.sodalive.audition.role.AuditionRoleDetailViewModel
 | 
				
			|||||||
import kr.co.vividnext.sodalive.chat.character.CharacterApi
 | 
					import kr.co.vividnext.sodalive.chat.character.CharacterApi
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.character.CharacterTabRepository
 | 
					import kr.co.vividnext.sodalive.chat.character.CharacterTabRepository
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.character.CharacterTabViewModel
 | 
					import kr.co.vividnext.sodalive.chat.character.CharacterTabViewModel
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentApi
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailRepository
 | 
					import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailRepository
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailViewModel
 | 
					import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailViewModel
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.talk.TalkApi
 | 
					import kr.co.vividnext.sodalive.chat.talk.TalkApi
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.talk.TalkTabRepository
 | 
					import kr.co.vividnext.sodalive.chat.talk.TalkTabRepository
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.talk.TalkTabViewModel
 | 
					import kr.co.vividnext.sodalive.chat.talk.TalkTabViewModel
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.chat.talk.room.chatTalkRoomModule
 | 
				
			||||||
import kr.co.vividnext.sodalive.common.ApiBuilder
 | 
					import kr.co.vividnext.sodalive.common.ApiBuilder
 | 
				
			||||||
import kr.co.vividnext.sodalive.common.ObjectBox
 | 
					import kr.co.vividnext.sodalive.common.ObjectBox
 | 
				
			||||||
import kr.co.vividnext.sodalive.explorer.ExplorerApi
 | 
					import kr.co.vividnext.sodalive.explorer.ExplorerApi
 | 
				
			||||||
@@ -146,7 +149,6 @@ import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagApi
 | 
				
			|||||||
import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagRepository
 | 
					import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagRepository
 | 
				
			||||||
import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagViewModel
 | 
					import kr.co.vividnext.sodalive.mypage.profile.tag.MemberTagViewModel
 | 
				
			||||||
import kr.co.vividnext.sodalive.mypage.recent.recentContentModule
 | 
					import kr.co.vividnext.sodalive.mypage.recent.recentContentModule
 | 
				
			||||||
import kr.co.vividnext.sodalive.chat.talk.room.chatTalkRoomModule
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.mypage.service_center.FaqApi
 | 
					import kr.co.vividnext.sodalive.mypage.service_center.FaqApi
 | 
				
			||||||
import kr.co.vividnext.sodalive.mypage.service_center.FaqRepository
 | 
					import kr.co.vividnext.sodalive.mypage.service_center.FaqRepository
 | 
				
			||||||
import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterViewModel
 | 
					import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterViewModel
 | 
				
			||||||
@@ -256,6 +258,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
 | 
				
			|||||||
        single { ApiBuilder().build(get(), HomeApi::class.java) }
 | 
					        single { ApiBuilder().build(get(), HomeApi::class.java) }
 | 
				
			||||||
        single { ApiBuilder().build(get(), CharacterApi::class.java) }
 | 
					        single { ApiBuilder().build(get(), CharacterApi::class.java) }
 | 
				
			||||||
        single { ApiBuilder().build(get(), TalkApi::class.java) }
 | 
					        single { ApiBuilder().build(get(), TalkApi::class.java) }
 | 
				
			||||||
 | 
					        single { ApiBuilder().build(get(), CharacterCommentApi::class.java) }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val viewModelModule = module {
 | 
					    private val viewModelModule = module {
 | 
				
			||||||
@@ -403,6 +406,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
 | 
				
			|||||||
        factory { CharacterTabRepository(get()) }
 | 
					        factory { CharacterTabRepository(get()) }
 | 
				
			||||||
        factory { CharacterDetailRepository(get(), get()) }
 | 
					        factory { CharacterDetailRepository(get(), get()) }
 | 
				
			||||||
        factory { TalkTabRepository(get()) }
 | 
					        factory { TalkTabRepository(get()) }
 | 
				
			||||||
 | 
					        factory { CharacterCommentRepository(get()) }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user