diff --git a/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt b/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt index 2b6c04f..f243913 100644 --- a/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt +++ b/app/src/main/java/com/example/login_v3/data/api/api_class/DM_Basic.kt @@ -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 = emptyList() // 若 API 沒給此欄位,給予空列表 + @Json(name = "reactions") val reactions: List? = emptyList() ){ @JsonClass(generateAdapter = true) data class Attachment( @@ -116,9 +116,6 @@ data class Message( ) } - - - //send message @JsonClass(generateAdapter = true) data class SendMessageRequest( diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt index 8b69c36..36660e4 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt @@ -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, modifier: Modifier = Modifier ) { - val currentUserId = "user_123" LazyColumn( modifier = modifier.fillMaxSize(), diff --git a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt index f58adf3..88a1737 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt @@ -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 + val messages: List, + 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.Loading) val uiState: StateFlow = _uiState.asStateFlow() + //message var + private val _sendMessageState = MutableStateFlow(SendMessageState.Idle) + val sendMessageState: StateFlow = _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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt index eb2283a..2a0d106 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Message_ViewModel.kt @@ -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>(emptyList()) val roomsState: StateFlow> = _roomsState - // 1. 新增:發送訊息的 UI 狀態 Flow - private val _sendMessageState = MutableStateFlow(SendMessageState.Idle) - val sendMessageState: StateFlow = _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) {