From 8de0dc224281efba3ad301b6f1a00401bfe6fe6f Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 11 Sep 2025 14:38:29 +0900 Subject: [PATCH] =?UTF-8?q?feat(chat):=20Talk=20=ED=83=AD=EC=97=90=20Recyc?= =?UTF-8?q?lerView=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/chat/room/list 호출에 page 파라미터 적용 (0부터 시작) - ViewModel에 currentPage/lastPageReached 상태 추가 및 append 로직 구현 - Fragment에 스크롤 리스너로 바닥 근접 시 다음 페이지 자동 로드 - 빈 데이터 시 마지막 페이지로 간주하여 추가 로딩 중단 --- app/build.gradle | 4 +-- .../vividnext/sodalive/chat/talk/TalkApi.kt | 3 +- .../sodalive/chat/talk/TalkTabFragment.kt | 22 ++++++++++-- .../sodalive/chat/talk/TalkTabRepository.kt | 5 +-- .../sodalive/chat/talk/TalkTabViewModel.kt | 36 ++++++++++++++++--- 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0da44574..aef18814 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,8 +35,8 @@ android { applicationId "kr.co.vividnext.sodalive" minSdk 23 targetSdk 34 - versionCode 181 - versionName "1.41.0" + versionCode 184 + versionName "1.42.0" } buildTypes { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt index 980becb2..eb4107c8 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt @@ -23,7 +23,8 @@ import retrofit2.http.Query interface TalkApi { @GET("/api/chat/room/list") fun getTalkRooms( - @Header("Authorization") authHeader: String + @Header("Authorization") authHeader: String, + @Query("page") page: Int ): Single>> @POST("/api/chat/room/create") diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabFragment.kt index cf33b489..e4d5b884 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabFragment.kt @@ -29,8 +29,8 @@ class TalkTabFragment : BaseFragment( setupRecyclerView() observeViewModel() - // 데이터 로드 - viewModel.loadTalkRooms() + // 데이터 로드 (스크롤 페이지네이션: 0페이지부터 시작) + viewModel.refreshTalkRooms() } private fun setupRecyclerView() { @@ -78,8 +78,24 @@ class TalkTabFragment : BaseFragment( }) recyclerView.apply { - layoutManager = LinearLayoutManager(requireContext()) + val lm = LinearLayoutManager(requireContext()) + layoutManager = lm adapter = this@TalkTabFragment.adapter + + // 스크롤 로딩 리스너: 끝에 도달하면 다음 페이지 로드 + addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + if (dy <= 0) return // 아래로 스크롤할 때만 체크 + + val totalItemCount = lm.itemCount + val lastVisible = lm.findLastVisibleItemPosition() + val threshold = 3 // 바닥 3개 전에서 미리 로드 + if (totalItemCount > 0 && lastVisible >= totalItemCount - 1 - threshold) { + viewModel.loadNextPage() + } + } + }) } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabRepository.kt index 98693f4f..5cd05592 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabRepository.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.chat.talk class TalkTabRepository(private val api: TalkApi) { fun getTalkRooms( - token: String - ) = api.getTalkRooms(authHeader = token) + token: String, + page: Int + ) = api.getTalkRooms(authHeader = token, page = page) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabViewModel.kt index f4a51ca6..3b3d586d 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkTabViewModel.kt @@ -20,25 +20,51 @@ class TalkTabViewModel(private val repository: TalkTabRepository) : BaseViewMode val toastLiveData: LiveData get() = _toastLiveData - fun loadTalkRooms() { + private var currentPage: Int = 0 + private var lastPageReached: Boolean = false + + /** + * 초기화 후 첫 페이지를 로드한다. page는 0부터 시작한다. + */ + fun refreshTalkRooms() { + currentPage = 0 + lastPageReached = false + _talkRooms.value = emptyList() + loadNextPage() + } + + /** + * 다음 페이지를 로드한다. 빈 데이터를 반환하면 마지막 페이지로 간주한다. + */ + fun loadNextPage() { + if (_isLoading.value == true || lastPageReached) return _isLoading.value = true + val token = "Bearer ${SharedPreferenceManager.token}" compositeDisposable.add( - repository.getTalkRooms(token = "Bearer ${SharedPreferenceManager.token}") + repository.getTalkRooms(token = token, page = currentPage) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { response -> _isLoading.value = false if (response.success) { - _talkRooms.value = response.data ?: emptyList() + val newItems = response.data ?: emptyList() + if (newItems.isEmpty()) { + // 마지막 페이지 도달 + lastPageReached = true + } else { + val current = _talkRooms.value.orEmpty() + _talkRooms.value = current + newItems + currentPage += 1 + } } else { _toastLiveData.value = response.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." } }, - { + { throwable -> _isLoading.value = false - it.message?.let { message -> Logger.e(message) } + throwable.message?.let { message -> Logger.e(message) } _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." } )