feat(chat-room): sendMessage 응답 다건 변경 반영

- TalkApi.sendMessage: ApiResponse<List<ServerChatMessage>>로 변경
  - ChatRepository.sendMessage: Single<List<ServerChatMessage>>로 변경. 로컬 SENDING→SENT 업데이트 후, 응답 메시지 전체를 DB에 저장
  - ChatRoomActivity: 구독부에서 List를 처리하며 mine == false(AI) 메시지들만 순서대로 append. 타이핑 인디케이터는 성공/실패 시 동일하게 제거
This commit is contained in:
2025-08-14 20:23:21 +09:00
parent 02747c539b
commit 09b8979ba0
3 changed files with 16 additions and 14 deletions

View File

@@ -40,7 +40,7 @@ interface TalkApi {
@Header("Authorization") authHeader: String, @Header("Authorization") authHeader: String,
@Path("roomId") roomId: Long, @Path("roomId") roomId: Long,
@Body request: SendMessageRequest @Body request: SendMessageRequest
): Single<ApiResponse<ServerChatMessage>> ): Single<ApiResponse<List<ServerChatMessage>>>
// 점진적 메시지 로딩 API // 점진적 메시지 로딩 API
@GET("/api/chat/rooms/{roomId}/messages") @GET("/api/chat/rooms/{roomId}/messages")

View File

@@ -30,7 +30,7 @@ class ChatRepository(
roomId: Long, roomId: Long,
localId: String, localId: String,
content: String content: String
): Single<ServerChatMessage> { ): Single<List<ServerChatMessage>> {
return talkApi.sendMessage( return talkApi.sendMessage(
authHeader = token, authHeader = token,
roomId = roomId, roomId = roomId,
@@ -38,18 +38,18 @@ class ChatRepository(
) )
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.map { ensureSuccess(it) } .map { ensureSuccess(it) }
.flatMap { serverMsg -> .flatMap { serverMsgs ->
// 1) 로컬에 사용자 메시지 상태를 SENT로 업데이트 // 1) 로컬에 사용자 메시지 상태를 SENT로 업데이트
val updateStatus = Completable.fromAction { val updateStatus = Completable.fromAction {
kotlinx.coroutines.runBlocking { chatDao.updateStatusByLocalId(roomId, localId, MessageStatus.SENT.name) } kotlinx.coroutines.runBlocking { chatDao.updateStatusByLocalId(roomId, localId, MessageStatus.SENT.name) }
} }
// 2) 서버 응답 메시지 로컬 DB에 저장(중복 방지: 동일 ID는 REPLACE) // 2) 서버 응답 메시지들을 로컬 DB에 저장(중복 방지: 동일 ID는 REPLACE)
val insertServer = Completable.fromAction { val insertServers = Completable.fromAction {
val entity = serverMsg.toEntity(roomId) val entities = serverMsgs.map { it.toEntity(roomId) }
kotlinx.coroutines.runBlocking { chatDao.insertMessage(entity) } kotlinx.coroutines.runBlocking { chatDao.insertMessages(entities) }
} }
updateStatus.andThen(insertServer) updateStatus.andThen(insertServers)
.andThen(Single.just(serverMsg)) .andThen(Single.just(serverMsgs))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
} }
} }

View File

@@ -285,15 +285,17 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
content = content content = content
) )
.observeOn(io.reactivex.rxjava3.android.schedulers.AndroidSchedulers.mainThread()) .observeOn(io.reactivex.rxjava3.android.schedulers.AndroidSchedulers.mainThread())
.subscribe({ serverMsg -> .subscribe({ serverMsgs ->
// 성공: 타이핑 인디케이터 제거 및 상태 업데이트 // 성공: 타이핑 인디케이터 제거 및 상태 업데이트
chatAdapter.hideTypingIndicator() chatAdapter.hideTypingIndicator()
updateUserMessageStatus(localId, MessageStatus.SENT) updateUserMessageStatus(localId, MessageStatus.SENT)
// 서버 응답이 AI 메시지인 경우 리스트에 추가 // 서버 응답이 여러 개인 경우, mine == false(AI)만 순서대로 추가
val domain = serverMsg.toDomain() serverMsgs.forEach { msg ->
if (!domain.mine) { val domain = msg.toDomain()
appendMessage(ChatListItem.AiMessage(domain, characterInfo?.name)) if (!domain.mine) {
appendMessage(ChatListItem.AiMessage(domain, characterInfo?.name))
}
} }
}, { error -> }, { error ->
// 실패: 타이핑 인디케이터 제거 및 FAILED로 업데이트 // 실패: 타이핑 인디케이터 제거 및 FAILED로 업데이트