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"
 | 
			
		||||
        minSdk 23
 | 
			
		||||
        targetSdk 34
 | 
			
		||||
        versionCode 181
 | 
			
		||||
        versionName "1.41.0"
 | 
			
		||||
        versionCode 184
 | 
			
		||||
        versionName "1.42.0"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buildTypes {
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user