feat(chat): Talk 탭에 RecyclerView 스크롤 페이지네이션 추가

- /api/chat/room/list 호출에 page 파라미터 적용 (0부터 시작)
- ViewModel에 currentPage/lastPageReached 상태 추가 및 append 로직 구현
- Fragment에 스크롤 리스너로 바닥 근접 시 다음 페이지 자동 로드
- 빈 데이터 시 마지막 페이지로 간주하여 추가 로딩 중단
This commit is contained in:
2025-09-11 14:38:29 +09:00
parent 56e99912d4
commit 8de0dc2242
5 changed files with 57 additions and 13 deletions

View File

@@ -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<ApiResponse<List<TalkRoom>>>
@POST("/api/chat/room/create")

View File

@@ -29,8 +29,8 @@ class TalkTabFragment : BaseFragment<FragmentTalkTabBinding>(
setupRecyclerView()
observeViewModel()
// 데이터 로드
viewModel.loadTalkRooms()
// 데이터 로드 (스크롤 페이지네이션: 0페이지부터 시작)
viewModel.refreshTalkRooms()
}
private fun setupRecyclerView() {
@@ -78,8 +78,24 @@ class TalkTabFragment : BaseFragment<FragmentTalkTabBinding>(
})
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()
}
}
})
}
}

View File

@@ -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)
}

View File

@@ -20,25 +20,51 @@ class TalkTabViewModel(private val repository: TalkTabRepository) : BaseViewMode
val toastLiveData: LiveData<String?>
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 = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
}
)