refactor(dm): 채팅 화면 전송을 WebSocket으로 전환한다

This commit is contained in:
2026-06-18 18:25:43 +09:00
parent deba733522
commit dd7a6465c1
4 changed files with 168 additions and 253 deletions

View File

@@ -52,7 +52,7 @@ class DmChatRoomActivity : BaseActivity<ActivityDmChatRoomBinding>(
override fun onStop() {
isStarted = false
viewModel.disconnectRealtime()
viewModel.leaveRealtime()
super.onStop()
}

View File

@@ -13,12 +13,12 @@ import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.CreateDmChatRoomResponse
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatEventClient
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatMessageResponse
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatMessagesPageResponse
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatRepository
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatRoomOpenResponse
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.SendDmChatMessageResponse
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatSocketClient
import kr.co.vividnext.sodalive.v2.main.chat.dm.data.DmChatSocketEvent
import kr.co.vividnext.sodalive.v2.main.chat.dm.model.DmChatMessageStatus
import kr.co.vividnext.sodalive.v2.main.chat.dm.model.DmChatMessageUiItem
import kr.co.vividnext.sodalive.v2.main.chat.dm.model.DmChatRoomUiState
@@ -46,7 +46,6 @@ class DmChatRoomViewModel(
private var shouldReconnectRealtime: Boolean = false
private var currentAuthToken: String = ""
private var currentRealtimeToken: String = ""
private var isDisconnecting: Boolean = false
private var reconnectDisposable: Disposable? = null
private var localMessageSequence: Long = 0L
private val mainHandler = Handler(Looper.getMainLooper())
@@ -187,16 +186,11 @@ class DmChatRoomViewModel(
shouldReconnectRealtime = true
reconnectDisposable?.dispose()
reconnectDisposable = null
repository.connectRealtime(
repository.connectSocket(
token = token,
roomId = roomId,
listener = object : DmChatEventClient.Listener {
override fun onConnected() {
scheduleRealtimeCallback { syncLatestMessagesAfterReconnect(token = token) }
}
override fun onMessage(message: DmChatMessageResponse) {
scheduleRealtimeCallback { onRealtimeMessage(message) }
listener = object : DmChatSocketClient.Listener {
override fun onEvent(event: DmChatSocketEvent) {
scheduleRealtimeCallback { handleSocketEvent(event, token) }
}
override fun onFailure(throwable: Throwable) {
@@ -208,9 +202,10 @@ class DmChatRoomViewModel(
}
}
)
repository.sendJoinRoom(roomId)
}
fun disconnectRealtime() {
fun leaveRealtime() {
val roomId = currentRoomId
if (roomId <= 0L) return
@@ -219,22 +214,8 @@ class DmChatRoomViewModel(
isRealtimeConnected = false
reconnectDisposable?.dispose()
reconnectDisposable = null
repository.cancelRealtime()
if (isDisconnecting) return
isDisconnecting = true
compositeDisposable.add(
repository.disconnectRealtime(token = authToken(), roomId = roomId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ isDisconnecting = false },
{
isDisconnecting = false
it.message?.let { message -> Logger.e(message) }
}
)
)
repository.sendLeaveRoom(roomId)
repository.closeSocket()
}
private fun scheduleRealtimeReconnect() {
@@ -266,7 +247,7 @@ class DmChatRoomViewModel(
mainHandler.removeCallbacksAndMessages(null)
reconnectDisposable?.dispose()
reconnectDisposable = null
repository.cancelRealtime()
repository.closeSocket()
super.onCleared()
}
@@ -302,22 +283,12 @@ class DmChatRoomViewModel(
}
private fun sendLocalMessage(localId: String, text: String) {
compositeDisposable.add(
repository.sendTextMessage(
token = authToken(),
roomId = currentRoomId,
textMessage = text
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ handleSendResult(localId, it) },
{
it.message?.let { message -> Logger.e(message) }
markLocalMessageFailed(localId)
}
)
val sent = repository.sendSocketText(
roomId = currentRoomId,
requestId = localId,
textMessage = text
)
if (!sent) markLocalMessageFailed(localId)
}
private fun handleOpenRoomResult(response: ApiResponse<DmChatRoomOpenResponse>) {
@@ -356,12 +327,18 @@ class DmChatRoomViewModel(
emitContent()
}
private fun handleSendResult(
localId: String,
response: ApiResponse<SendDmChatMessageResponse>
) {
val message = response.data?.message
val sentItem = if (response.success && message != null) message.toUiItem() else null
private fun handleSocketEvent(event: DmChatSocketEvent, token: String) {
when (event) {
DmChatSocketEvent.Joined -> syncLatestMessagesAfterReconnect(token = token)
is DmChatSocketEvent.Message -> onRealtimeMessage(event.message)
is DmChatSocketEvent.SendAck -> handleSendAck(event.requestId, event.message)
is DmChatSocketEvent.Error -> event.requestId?.let { markLocalMessageFailed(it) }
DmChatSocketEvent.Pong -> Unit
}
}
private fun handleSendAck(localId: String, message: DmChatMessageResponse) {
val sentItem = message.toUiItem()
if (sentItem == null) {
markLocalMessageFailed(localId)
return