feat(chat): Talk 탭에 RecyclerView 스크롤 페이지네이션 추가
- /api/chat/room/list 호출에 page 파라미터 적용 (0부터 시작) - ViewModel에 currentPage/lastPageReached 상태 추가 및 append 로직 구현 - Fragment에 스크롤 리스너로 바닥 근접 시 다음 페이지 자동 로드 - 빈 데이터 시 마지막 페이지로 간주하여 추가 로딩 중단
This commit is contained in:
@@ -35,8 +35,8 @@ android {
|
|||||||
applicationId "kr.co.vividnext.sodalive"
|
applicationId "kr.co.vividnext.sodalive"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 181
|
versionCode 184
|
||||||
versionName "1.41.0"
|
versionName "1.42.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ import retrofit2.http.Query
|
|||||||
interface TalkApi {
|
interface TalkApi {
|
||||||
@GET("/api/chat/room/list")
|
@GET("/api/chat/room/list")
|
||||||
fun getTalkRooms(
|
fun getTalkRooms(
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String,
|
||||||
|
@Query("page") page: Int
|
||||||
): Single<ApiResponse<List<TalkRoom>>>
|
): Single<ApiResponse<List<TalkRoom>>>
|
||||||
|
|
||||||
@POST("/api/chat/room/create")
|
@POST("/api/chat/room/create")
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ class TalkTabFragment : BaseFragment<FragmentTalkTabBinding>(
|
|||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
observeViewModel()
|
observeViewModel()
|
||||||
|
|
||||||
// 데이터 로드
|
// 데이터 로드 (스크롤 페이지네이션: 0페이지부터 시작)
|
||||||
viewModel.loadTalkRooms()
|
viewModel.refreshTalkRooms()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
@@ -78,8 +78,24 @@ class TalkTabFragment : BaseFragment<FragmentTalkTabBinding>(
|
|||||||
})
|
})
|
||||||
|
|
||||||
recyclerView.apply {
|
recyclerView.apply {
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
val lm = LinearLayoutManager(requireContext())
|
||||||
|
layoutManager = lm
|
||||||
adapter = this@TalkTabFragment.adapter
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.chat.talk
|
|||||||
|
|
||||||
class TalkTabRepository(private val api: TalkApi) {
|
class TalkTabRepository(private val api: TalkApi) {
|
||||||
fun getTalkRooms(
|
fun getTalkRooms(
|
||||||
token: String
|
token: String,
|
||||||
) = api.getTalkRooms(authHeader = token)
|
page: Int
|
||||||
|
) = api.getTalkRooms(authHeader = token, page = page)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,25 +20,51 @@ class TalkTabViewModel(private val repository: TalkTabRepository) : BaseViewMode
|
|||||||
val toastLiveData: LiveData<String?>
|
val toastLiveData: LiveData<String?>
|
||||||
get() = _toastLiveData
|
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
|
_isLoading.value = true
|
||||||
|
val token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
repository.getTalkRooms(token = "Bearer ${SharedPreferenceManager.token}")
|
repository.getTalkRooms(token = token, page = currentPage)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ response ->
|
{ response ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
if (response.success) {
|
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 {
|
} else {
|
||||||
_toastLiveData.value =
|
_toastLiveData.value =
|
||||||
response.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
response.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{ throwable ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
it.message?.let { message -> Logger.e(message) }
|
throwable.message?.let { message -> Logger.e(message) }
|
||||||
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user