This commit is contained in:
Eric85710 2026-05-24 16:52:41 +08:00
parent b8a5ace88a
commit 8aabfd39dc
4 changed files with 91 additions and 51 deletions

View file

@ -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(

View file

@ -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(),

View file

@ -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
}
}

View file

@ -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) {