feat(chat-room): 7.2 점진적 메시지 로딩 구현 및 중복 방지 처리
- 상단 스크롤 시 loadMoreMessages로 이전 메시지 로드 - 커서(timestamp) 기반 페이징 및 hasMore/nextCursor 상태 갱신 - messageId 기반 중복 제거, prepend 시 스크롤 위치 보정
This commit is contained in:
@@ -16,6 +16,7 @@ import coil.load
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterType
|
import kr.co.vividnext.sodalive.chat.character.detail.CharacterType
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding
|
import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
@@ -435,16 +436,65 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 상단 도달 시 이전 메시지를 로드한다. Repository 연동은 추후(7.x) 예정. */
|
/** 상단 도달 시 이전(더 오래된) 메시지를 서버에서 불러와 상단에 추가한다. */
|
||||||
private fun loadMoreMessages() {
|
private fun loadMoreMessages() {
|
||||||
|
if (isLoading) return
|
||||||
isLoading = true
|
isLoading = true
|
||||||
// TODO: 7.x에서 Repository 연동하여 서버에서 가져오기. 여기서는 구조만 구현.
|
|
||||||
// 예시: 가장 오래된 메시지 createdAt을 커서로 사용.
|
|
||||||
// val cursor = (items.lastOrNull() as? ChatListItem.UserMessage)?.data?.createdAt
|
|
||||||
|
|
||||||
// 현재 단계에서는 더 로드할 메시지가 없다고 가정하고 즉시 종료
|
val token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
// 커서: API에서 내려준 nextCursor 우선, 없으면 현재 목록 중 가장 오래된 createdAt
|
||||||
|
val fallbackOldestCreatedAt: Long? = items.firstOrNull()?.let {
|
||||||
|
when (it) {
|
||||||
|
is ChatListItem.UserMessage -> it.data.createdAt
|
||||||
|
is ChatListItem.AiMessage -> it.data.createdAt
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val cursor: Long? = nextCursor ?: fallbackOldestCreatedAt
|
||||||
|
|
||||||
|
val disposable = chatRepository.loadMoreMessages(token = token, roomId = roomId, cursor = cursor)
|
||||||
|
.observeOn(io.reactivex.rxjava3.android.schedulers.AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ response ->
|
||||||
|
// 서버에서 받은 메시지(이전 것들)를 오래된 -> 최신 순으로 정렬
|
||||||
|
val sorted = response.messages.sortedBy { it.createdAt }
|
||||||
|
|
||||||
|
// 중복 제거: 기존 목록의 messageId 집합과 비교
|
||||||
|
val existingIds: Set<Long> = items.mapNotNull {
|
||||||
|
when (it) {
|
||||||
|
is ChatListItem.UserMessage -> it.data.messageId
|
||||||
|
is ChatListItem.AiMessage -> it.data.messageId
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
val newChatItems: List<ChatListItem> = sorted
|
||||||
|
.map { it.toDomain() }
|
||||||
|
.filter { !existingIds.contains(it.messageId) }
|
||||||
|
.map { domain ->
|
||||||
|
if (domain.mine) ChatListItem.UserMessage(domain)
|
||||||
|
else ChatListItem.AiMessage(domain, characterInfo?.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상단에 추가하면서 스크롤 위치 보정
|
||||||
|
prependMessages(newChatItems)
|
||||||
|
|
||||||
|
// 페이징 상태 갱신
|
||||||
|
hasMoreMessages = response.hasMore
|
||||||
|
nextCursor = response.nextCursor ?: newChatItems.firstOrNull()?.let {
|
||||||
|
when (it) {
|
||||||
|
is ChatListItem.UserMessage -> it.data.createdAt
|
||||||
|
is ChatListItem.AiMessage -> it.data.createdAt
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isLoading = false
|
isLoading = false
|
||||||
hasMoreMessages = false
|
}, { error ->
|
||||||
|
isLoading = false
|
||||||
|
showToast(error.message ?: "이전 메시지를 불러오지 못했습니다.")
|
||||||
|
})
|
||||||
|
|
||||||
|
compositeDisposable.add(disposable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user