dm-30
This commit is contained in:
parent
b8a5ace88a
commit
8aabfd39dc
4 changed files with 91 additions and 51 deletions
|
|
@ -100,7 +100,7 @@ data class Message(
|
|||
@Json(name = "forwarded_from_id") val forwardedFromId: String? = null,
|
||||
@Json(name = "forwarded_from_room_id") val forwardedFromRoomId: String? = null,
|
||||
@Json(name = "attachment") val attachment: Attachment? = null,
|
||||
@Json(name = "reactions") val reactions: List<Reaction> = emptyList() // 若 API 沒給此欄位,給予空列表
|
||||
@Json(name = "reactions") val reactions: List<Reaction>? = emptyList()
|
||||
){
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Attachment(
|
||||
|
|
@ -116,9 +116,6 @@ data class Message(
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//send message
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SendMessageRequest(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.example.login_v3.home.Message.UI.Detail
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -57,6 +58,7 @@ import com.example.login_v3.R
|
|||
import com.example.login_v3.data.api.api_class.Message
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.ChatViewModel
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.MessagesUiState
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.SendMessageState
|
||||
import com.example.login_v3.home.Message.ViewModel.UserStatus
|
||||
import com.example.login_v3.navigation.BottomBarViewModel
|
||||
import com.example.login_v3.navigation.Screen
|
||||
|
|
@ -72,7 +74,11 @@ fun MessageMessaging(
|
|||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
//send message
|
||||
val sendStatus by viewModel.sendMessageState.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
|
||||
//進入時重整
|
||||
LaunchedEffect(roomId) {
|
||||
viewModel.loadMessages(roomId)
|
||||
}
|
||||
|
|
@ -82,6 +88,22 @@ fun MessageMessaging(
|
|||
onDispose { bottomBarViewModel.setVisible(true) }
|
||||
}
|
||||
|
||||
// ✨ 監聽發送狀態,如果失敗了就彈出提示並重置狀態
|
||||
LaunchedEffect(sendStatus) {
|
||||
if (sendStatus is SendMessageState.Error) {
|
||||
val errorMsg = (sendStatus as SendMessageState.Error).message
|
||||
Toast.makeText(context, "發送失敗: $errorMsg", Toast.LENGTH_SHORT).show()
|
||||
viewModel.resetSendMessageState()
|
||||
} else if (sendStatus is SendMessageState.Success) {
|
||||
// 發送成功後,重置狀態以便下一次發送
|
||||
viewModel.resetSendMessageState()
|
||||
|
||||
// 【優化體驗】:你可以在這裡重新呼叫 viewModel.loadMessages(roomId) 刷新列表
|
||||
// 或者如果你的 ViewModel 已經會自動把新訊息塞進 uiState,這裡就什麼都不用做。
|
||||
viewModel.loadMessages(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
// 根據目前的 uiState 來動態決定 TopBar 要顯示什麼文字
|
||||
val topBarTitle = when (val state = uiState) {
|
||||
is MessagesUiState.Success -> state.roomTitle
|
||||
|
|
@ -136,6 +158,15 @@ fun MessageMessaging(
|
|||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
MessageInputBar(
|
||||
isLoading = sendStatus is SendMessageState.Loading,
|
||||
onSendClick = { text ->
|
||||
// 呼叫 ViewModel 的發送功能
|
||||
viewModel.sendMessage(roomId = roomId, content = text)
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(
|
||||
|
|
@ -155,6 +186,7 @@ fun MessageMessaging(
|
|||
Text("目前沒有新訊息")
|
||||
} else {
|
||||
MessageList(
|
||||
currentUserId = state.currentUserId,
|
||||
messages = state.messages,
|
||||
partnerAvatarUrl = state.partnerAvatarUrl,
|
||||
partnerDisplayName = state.roomTitle
|
||||
|
|
@ -174,12 +206,12 @@ fun MessageMessaging(
|
|||
|
||||
@Composable
|
||||
fun MessageList(
|
||||
currentUserId: String,
|
||||
partnerAvatarUrl: Any?,
|
||||
partnerDisplayName: String,
|
||||
messages: List<Message>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val currentUserId = "user_123"
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
|
|
|
|||
|
|
@ -17,13 +17,23 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
|
||||
//message sending state
|
||||
sealed class SendMessageState {
|
||||
object Idle : SendMessageState() // 閒置狀態(預設)
|
||||
object Loading : SendMessageState() // 發送中
|
||||
data class Success(val message: Message) : SendMessageState() // 發送成功
|
||||
data class Error(val message: String) : SendMessageState() // 發送失敗
|
||||
}
|
||||
|
||||
|
||||
sealed interface MessagesUiState {
|
||||
object Loading : MessagesUiState
|
||||
data class Success(
|
||||
val roomTitle: String,
|
||||
val partnerStatus: UserStatus,
|
||||
val partnerAvatarUrl: Any?,
|
||||
val messages: List<Message>
|
||||
val messages: List<Message>,
|
||||
val currentUserId: String
|
||||
) : MessagesUiState
|
||||
|
||||
data class Error(val message: String) : MessagesUiState
|
||||
|
|
@ -34,9 +44,14 @@ class ChatViewModel @Inject constructor(
|
|||
private val repository: ChatRoomsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
//UI state var
|
||||
private val _uiState = MutableStateFlow<MessagesUiState>(MessagesUiState.Loading)
|
||||
val uiState: StateFlow<MessagesUiState> = _uiState.asStateFlow()
|
||||
|
||||
//message var
|
||||
private val _sendMessageState = MutableStateFlow<SendMessageState>(SendMessageState.Idle)
|
||||
val sendMessageState: StateFlow<SendMessageState> = _sendMessageState
|
||||
|
||||
fun loadMessages(roomId: String) {
|
||||
viewModelScope.launch {
|
||||
_uiState.value = MessagesUiState.Loading
|
||||
|
|
@ -62,11 +77,15 @@ class ChatViewModel @Inject constructor(
|
|||
Triple("聊天室", UserStatus.UNKNOWN, R.drawable.avatar_v1)
|
||||
}
|
||||
|
||||
|
||||
val currentLoggedInUserId = "user_123"
|
||||
|
||||
_uiState.value = MessagesUiState.Success(
|
||||
roomTitle = title,
|
||||
partnerStatus = status,
|
||||
partnerAvatarUrl = avatarUrl, // ✨ 這裡就會是完整的網址或 R.drawable.avatar_v1
|
||||
messages = messagesResponse.messages
|
||||
partnerAvatarUrl = avatarUrl, //這裡就會是完整的網址或 R.drawable.avatar_v1
|
||||
messages = messagesResponse.messages,
|
||||
currentUserId = currentLoggedInUserId
|
||||
)
|
||||
|
||||
//已讀功能
|
||||
|
|
@ -84,4 +103,38 @@ class ChatViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun sendMessage(roomId: String, content: String, replyToId: String? = null) {
|
||||
if (content.isBlank()) return
|
||||
|
||||
viewModelScope.launch {
|
||||
_sendMessageState.value = SendMessageState.Loading
|
||||
|
||||
val result = repository.sendMessage(roomId, content, replyToId)
|
||||
|
||||
result.onSuccess { newMessage ->
|
||||
_sendMessageState.value = SendMessageState.Success(newMessage)
|
||||
|
||||
// 😎【超讚的體驗優化】:發送成功後,直接把新訊息手動塞進現有的 UI 列表裡
|
||||
val currentState = _uiState.value
|
||||
if (currentState is MessagesUiState.Success) {
|
||||
// 把新訊息加到原本的列表最後面
|
||||
val updatedMessages = currentState.messages + newMessage
|
||||
|
||||
// 更新 uiState,這樣 Compose 畫面不需要重新 load 網路就能立刻刷新!
|
||||
_uiState.value = currentState.copy(messages = updatedMessages)
|
||||
}
|
||||
|
||||
}.onFailure { error ->
|
||||
Log.e("ChatViewModel", "Send message failed", error)
|
||||
_sendMessageState.value = SendMessageState.Error(error.message ?: "未知錯誤")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✨ 4. 新增:重設發送狀態的 function
|
||||
fun resetSendMessageState() {
|
||||
_sendMessageState.value = SendMessageState.Idle
|
||||
}
|
||||
}
|
||||
|
|
@ -17,13 +17,7 @@ import javax.inject.Inject
|
|||
|
||||
|
||||
|
||||
//message sending state
|
||||
sealed class SendMessageState {
|
||||
object Idle : SendMessageState() // 閒置狀態(預設)
|
||||
object Loading : SendMessageState() // 發送中
|
||||
data class Success(val message: Message) : SendMessageState() // 發送成功,並帶回新訊息
|
||||
data class Error(val message: String) : SendMessageState() // 發送失敗,帶有錯誤訊息
|
||||
}
|
||||
|
||||
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -34,10 +28,6 @@ class ChatRoomsViewModel @Inject constructor(
|
|||
private val _roomsState = MutableStateFlow<List<ChatRoom>>(emptyList())
|
||||
val roomsState: StateFlow<List<ChatRoom>> = _roomsState
|
||||
|
||||
// 1. 新增:發送訊息的 UI 狀態 Flow
|
||||
private val _sendMessageState = MutableStateFlow<SendMessageState>(SendMessageState.Idle)
|
||||
val sendMessageState: StateFlow<SendMessageState> = _sendMessageState
|
||||
|
||||
fun loadRooms() {
|
||||
viewModelScope.launch {
|
||||
val result = repository.fetchRooms()
|
||||
|
|
@ -54,38 +44,6 @@ class ChatRoomsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 新增:發送訊息的 function
|
||||
fun sendMessage(roomId: String, content: String, replyToId: String? = null) {
|
||||
// 如果內容是空的,就直接攔截不送出
|
||||
if (content.isBlank()) return
|
||||
|
||||
viewModelScope.launch {
|
||||
// 切換成載入中狀態
|
||||
_sendMessageState.value = SendMessageState.Loading
|
||||
|
||||
// 呼叫 repository
|
||||
val result = repository.sendMessage(roomId, content, replyToId)
|
||||
|
||||
result.onSuccess { newMessage ->
|
||||
// 發送成功
|
||||
_sendMessageState.value = SendMessageState.Success(newMessage)
|
||||
|
||||
// 【可選】如果你的聊天訊息列表也是歸這個 ViewModel 管,
|
||||
// 你可以在這裡順便把新訊息加進當前的訊息列表 Flow 中,這樣畫面就會立刻跳出新訊息。
|
||||
|
||||
}.onFailure { error ->
|
||||
// 發送失敗
|
||||
Log.e("ChatRoomsViewModel", "Send message failed", error)
|
||||
_sendMessageState.value = SendMessageState.Error(error.message ?: "未知錯誤")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 新增:重設發送狀態(通常在 UI 顯示完錯誤跳出視窗、或成功送出清空輸入框後呼叫)
|
||||
fun resetSendMessageState() {
|
||||
_sendMessageState.value = SendMessageState.Idle
|
||||
}
|
||||
}
|
||||
|
||||
enum class UserStatus(val color: Color) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue