fix(chat): 대화 초기화 성공 시 로컬 데이터 삭제 및 로딩 다이얼로그 적용

- ChatMessageDao: deleteMessagesByRoomId(roomId) 추가
- ChatRepository: clearMessagesByRoom(roomId) 추가
- ChatRoomActivity:
  - clearLocalPrefsForRoom(roomId) 구현
  - reset 플로우에 Prefs/DB 삭제 체인 연결
  - onResetChatRequested()에서 LoadingDialog 표시 및 doFinally로 닫힘 보장
This commit is contained in:
2025-08-28 00:23:14 +09:00
parent e3bcc6d3a6
commit 5c78c567ca
4 changed files with 60 additions and 8 deletions

View File

@@ -4,6 +4,7 @@ import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.chat.talk.room.ChatMessagePurchaseRequest import kr.co.vividnext.sodalive.chat.talk.room.ChatMessagePurchaseRequest
import kr.co.vividnext.sodalive.chat.talk.room.ChatMessagesResponse import kr.co.vividnext.sodalive.chat.talk.room.ChatMessagesResponse
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomEnterResponse import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomEnterResponse
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomResetRequest
import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest
import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomResponse import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomResponse
import kr.co.vividnext.sodalive.chat.talk.room.SendChatMessageResponse import kr.co.vividnext.sodalive.chat.talk.room.SendChatMessageResponse
@@ -35,7 +36,8 @@ interface TalkApi {
@POST("/api/chat/room/{roomId}/reset") @POST("/api/chat/room/{roomId}/reset")
fun resetChatRoom( fun resetChatRoom(
@Header("Authorization") authHeader: String, @Header("Authorization") authHeader: String,
@Path("roomId") roomId: Long @Path("roomId") roomId: Long,
@Body request: ChatRoomResetRequest
): Single<ApiResponse<CreateChatRoomResponse>> ): Single<ApiResponse<CreateChatRoomResponse>>
// 통합 채팅방 입장 API // 통합 채팅방 입장 API

View File

@@ -43,7 +43,13 @@ class ChatRepository(
.flatMap { response -> .flatMap { response ->
// 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 insertServers = Completable.fromAction { val insertServers = Completable.fromAction {
@@ -86,8 +92,16 @@ class ChatRepository(
* 통합 채팅방 입장: 서버 캐릭터 정보 + 최신 메시지 수신 후 로컬 DB 업데이트 * 통합 채팅방 입장: 서버 캐릭터 정보 + 최신 메시지 수신 후 로컬 DB 업데이트
* - 로컬 데이터가 없더라도 서버 응답을 기준으로 동기화 * - 로컬 데이터가 없더라도 서버 응답을 기준으로 동기화
*/ */
fun enterChatRoom(token: String, roomId: Long, characterImageId: Long?): Single<ChatRoomEnterResponse> { fun enterChatRoom(
return talkApi.enterChatRoom(authHeader = token, roomId = roomId, characterImageId = characterImageId) token: String,
roomId: Long,
characterImageId: Long?
): Single<ChatRoomEnterResponse> {
return talkApi.enterChatRoom(
authHeader = token,
roomId = roomId,
characterImageId = characterImageId
)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.flatMap { response -> .flatMap { response ->
val data = ensureSuccess(response) val data = ensureSuccess(response)
@@ -106,7 +120,12 @@ class ChatRepository(
* 점진적 메시지 로딩: 커서 기반으로 이전 메시지를 조회 * 점진적 메시지 로딩: 커서 기반으로 이전 메시지를 조회
*/ */
fun loadMoreMessages(token: String, roomId: Long, cursor: Long?): Single<ChatMessagesResponse> { fun loadMoreMessages(token: String, roomId: Long, cursor: Long?): Single<ChatMessagesResponse> {
return talkApi.getChatRoomMessages(authHeader = token, roomId = roomId, cursor = cursor, limit = 20) return talkApi.getChatRoomMessages(
authHeader = token,
roomId = roomId,
cursor = cursor,
limit = 20
)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.flatMap { response -> .flatMap { response ->
val data = ensureMessagesSuccess(response) val data = ensureMessagesSuccess(response)
@@ -132,10 +151,20 @@ class ChatRepository(
/** /**
* localId 기반 상태 업데이트(SENDING→SENT/FAILED) * localId 기반 상태 업데이트(SENDING→SENT/FAILED)
*/ */
fun updateMessageStatusByLocalId(roomId: Long, localId: String, newStatus: MessageStatus): Completable { fun updateMessageStatusByLocalId(
roomId: Long,
localId: String,
newStatus: MessageStatus
): Completable {
return Completable.fromAction { return Completable.fromAction {
// enum을 문자열로 변환하여 쿼리 파라미터로 전달 // enum을 문자열로 변환하여 쿼리 파라미터로 전달
kotlinx.coroutines.runBlocking { chatDao.updateStatusByLocalId(roomId, localId, newStatus.name) } kotlinx.coroutines.runBlocking {
chatDao.updateStatusByLocalId(
roomId,
localId,
newStatus.name
)
}
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
} }
@@ -206,7 +235,11 @@ class ChatRepository(
* 대화 초기화: 서버에 리셋 요청 → 새 채팅방 ID 반환 * 대화 초기화: 서버에 리셋 요청 → 새 채팅방 ID 반환
*/ */
fun resetChatRoom(token: String, roomId: Long): Single<CreateChatRoomResponse> { fun resetChatRoom(token: String, roomId: Long): Single<CreateChatRoomResponse> {
return talkApi.resetChatRoom(authHeader = token, roomId = roomId) return talkApi.resetChatRoom(
authHeader = token,
roomId = roomId,
request = ChatRoomResetRequest()
)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.map { ensureSuccess(it) } .map { ensureSuccess(it) }
} }

View File

@@ -20,6 +20,7 @@ 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.base.SodaDialog import kr.co.vividnext.sodalive.base.SodaDialog
import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterType import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterType
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
@@ -890,6 +891,12 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
desc = desc, desc = desc,
confirmButtonTitle = "초기화", confirmButtonTitle = "초기화",
confirmButtonClick = { confirmButtonClick = {
val loadingDialog = LoadingDialog(this, layoutInflater)
loadingDialog.show(
screenWidth,
"대화 초기화 중..."
)
val token = "Bearer ${SharedPreferenceManager.token}" val token = "Bearer ${SharedPreferenceManager.token}"
val disposable = chatRepository.resetChatRoom(token = token, roomId = roomId) val disposable = chatRepository.resetChatRoom(token = token, roomId = roomId)
.flatMap { response -> .flatMap { response ->
@@ -899,6 +906,7 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
.andThen(io.reactivex.rxjava3.core.Single.just(response)) .andThen(io.reactivex.rxjava3.core.Single.just(response))
} }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doFinally { loadingDialog.dismiss() }
.subscribe({ response -> .subscribe({ response ->
val intent = newIntent(this, response.chatRoomId) val intent = newIntent(this, response.chatRoomId)
startActivity(intent) startActivity(intent)

View File

@@ -0,0 +1,9 @@
package kr.co.vividnext.sodalive.chat.talk.room
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class ChatRoomResetRequest(
@SerializedName("container") val container: String = "aos"
)