diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt index 2e7e4517..5a9f4bf3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRepository.kt @@ -44,6 +44,8 @@ class ChatRepository( val entities = data.messages.map { it.toEntity(roomId) } Single.fromCallable { kotlinx.coroutines.runBlocking { chatDao.insertMessages(entities) } + // 오래된 메시지 정리(최신 200개 유지) + kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, 200) } data }.subscribeOn(Schedulers.io()) } @@ -60,11 +62,50 @@ class ChatRepository( val entities = data.messages.map { it.toEntity(roomId) } Single.fromCallable { kotlinx.coroutines.runBlocking { chatDao.insertMessages(entities) } + // 오래된 메시지 정리(최신 200개 유지) + kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, 200) } data }.subscribeOn(Schedulers.io()) } } + /** + * 로컬 임시 메시지 저장(SENDING 등) + */ + fun saveLocalMessage(roomId: Long, message: ChatMessage): Completable { + return Completable.fromAction { + kotlinx.coroutines.runBlocking { chatDao.insertMessage(message.toEntity(roomId)) } + }.subscribeOn(Schedulers.io()) + } + + /** + * localId 기반 상태 업데이트(SENDING→SENT/FAILED) + */ + fun updateMessageStatusByLocalId(roomId: Long, localId: String, newStatus: MessageStatus): Completable { + return Completable.fromAction { + // enum을 문자열로 변환하여 쿼리 파라미터로 전달 + kotlinx.coroutines.runBlocking { chatDao.updateStatusByLocalId(roomId, localId, newStatus.name) } + }.subscribeOn(Schedulers.io()) + } + + /** + * 오래된 메시지 정리(방별 최신 keepLatest개 유지) + */ + fun trimOldMessages(roomId: Long, keepLatest: Int = 200): Completable { + return Completable.fromAction { + kotlinx.coroutines.runBlocking { trimOldMessagesInternal(roomId, keepLatest) } + }.subscribeOn(Schedulers.io()) + } + + private suspend fun trimOldMessagesInternal(roomId: Long, keepLatest: Int) { + if (keepLatest <= 0) return + val offset = keepLatest - 1 + val cutoff = chatDao.getNthLatestCreatedAt(roomId, offset) + if (cutoff != null) { + chatDao.deleteOldMessages(roomId, cutoff) + } + } + /** * 로그아웃 시 모든 로컬 메시지 삭제 */ diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt index 9cfecc90..b03359d7 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt @@ -270,6 +270,11 @@ class ChatRoomActivity : BaseActivity( isGrouped = false ) appendMessage(ChatListItem.UserMessage(userMsg)) + // DB에도 임시 메시지 저장(7.3) + compositeDisposable.add( + chatRepository.saveLocalMessage(roomId, userMsg) + .subscribe({ }, { /* 로컬 저장 실패는 UI 진행에 영향 주지 않음 */ }) + ) // 2) 타이핑 인디케이터 표시 chatAdapter.showTypingIndicator() @@ -301,6 +306,14 @@ class ChatRoomActivity : BaseActivity( items[index] = ChatListItem.UserMessage(updated) chatAdapter.setItems(items) autoScrollIfAtBottom() + + // DB에도 상태 업데이트 반영(7.3) + updated.localId?.let { lid -> + compositeDisposable.add( + chatRepository.updateMessageStatusByLocalId(roomId, lid, newStatus) + .subscribe({ }, { /* 실패 시 무시(다음 동기화 시 정합성 확보) */ }) + ) + } } } @@ -385,6 +398,12 @@ class ChatRoomActivity : BaseActivity( // 최신 메시지 위치로 scrollToBottom() isLoading = false + + // 7.3: 오래된 메시지 정리(백그라운드) + compositeDisposable.add( + chatRepository.trimOldMessages(roomId, keepLatest = 200) + .subscribe({ }, { /* 무시 */ }) + ) }, { error -> isLoading = false showToast(error.message ?: "채팅방 데이터를 불러오지 못했습니다.") @@ -489,6 +508,12 @@ class ChatRoomActivity : BaseActivity( } isLoading = false + + // 7.3: 오래된 메시지 정리(백그라운드) + compositeDisposable.add( + chatRepository.trimOldMessages(roomId, keepLatest = 200) + .subscribe({ }, { /* 무시 */ }) + ) }, { error -> isLoading = false showToast(error.message ?: "이전 메시지를 불러오지 못했습니다.") diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/db/ChatMessageDao.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/db/ChatMessageDao.kt index 54267951..19d3efbd 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/db/ChatMessageDao.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/db/ChatMessageDao.kt @@ -21,6 +21,14 @@ interface ChatMessageDao { @Update suspend fun updateMessage(message: ChatMessageEntity) + // 메시지 상태를 localId 기준으로 업데이트(Converter로 문자열 상태 저장) + @Query("UPDATE chat_messages SET status = :status WHERE roomId = :roomId AND localId = :localId") + suspend fun updateStatusByLocalId(roomId: Long, localId: String, status: String): Int + + // N번째 최신(createdAt DESC) 항목의 createdAt (OFFSET 사용) + @Query("SELECT createdAt FROM chat_messages WHERE roomId = :roomId ORDER BY createdAt DESC LIMIT 1 OFFSET :offset") + suspend fun getNthLatestCreatedAt(roomId: Long, offset: Int): Long? + @Query("DELETE FROM chat_messages WHERE roomId = :roomId AND createdAt < :cutoffTime") suspend fun deleteOldMessages(roomId: Long, cutoffTime: Long)