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_id") val forwardedFromId: String? = null,
|
||||||
@Json(name = "forwarded_from_room_id") val forwardedFromRoomId: String? = null,
|
@Json(name = "forwarded_from_room_id") val forwardedFromRoomId: String? = null,
|
||||||
@Json(name = "attachment") val attachment: Attachment? = 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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Attachment(
|
data class Attachment(
|
||||||
|
|
@ -116,9 +116,6 @@ data class Message(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//send message
|
//send message
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class SendMessageRequest(
|
data class SendMessageRequest(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.example.login_v3.home.Message.UI.Detail
|
package com.example.login_v3.home.Message.UI.Detail
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.data.api.api_class.Message
|
||||||
import com.example.login_v3.home.Message.ViewModel.Detail.ChatViewModel
|
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.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.home.Message.ViewModel.UserStatus
|
||||||
import com.example.login_v3.navigation.BottomBarViewModel
|
import com.example.login_v3.navigation.BottomBarViewModel
|
||||||
import com.example.login_v3.navigation.Screen
|
import com.example.login_v3.navigation.Screen
|
||||||
|
|
@ -72,7 +74,11 @@ fun MessageMessaging(
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
//send message
|
||||||
|
val sendStatus by viewModel.sendMessageState.collectAsStateWithLifecycle()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
//進入時重整
|
||||||
LaunchedEffect(roomId) {
|
LaunchedEffect(roomId) {
|
||||||
viewModel.loadMessages(roomId)
|
viewModel.loadMessages(roomId)
|
||||||
}
|
}
|
||||||
|
|
@ -82,6 +88,22 @@ fun MessageMessaging(
|
||||||
onDispose { bottomBarViewModel.setVisible(true) }
|
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 要顯示什麼文字
|
// 根據目前的 uiState 來動態決定 TopBar 要顯示什麼文字
|
||||||
val topBarTitle = when (val state = uiState) {
|
val topBarTitle = when (val state = uiState) {
|
||||||
is MessagesUiState.Success -> state.roomTitle
|
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 ->
|
) { innerPadding ->
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -155,6 +186,7 @@ fun MessageMessaging(
|
||||||
Text("目前沒有新訊息")
|
Text("目前沒有新訊息")
|
||||||
} else {
|
} else {
|
||||||
MessageList(
|
MessageList(
|
||||||
|
currentUserId = state.currentUserId,
|
||||||
messages = state.messages,
|
messages = state.messages,
|
||||||
partnerAvatarUrl = state.partnerAvatarUrl,
|
partnerAvatarUrl = state.partnerAvatarUrl,
|
||||||
partnerDisplayName = state.roomTitle
|
partnerDisplayName = state.roomTitle
|
||||||
|
|
@ -174,12 +206,12 @@ fun MessageMessaging(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageList(
|
fun MessageList(
|
||||||
|
currentUserId: String,
|
||||||
partnerAvatarUrl: Any?,
|
partnerAvatarUrl: Any?,
|
||||||
partnerDisplayName: String,
|
partnerDisplayName: String,
|
||||||
messages: List<Message>,
|
messages: List<Message>,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val currentUserId = "user_123"
|
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,23 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
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 {
|
sealed interface MessagesUiState {
|
||||||
object Loading : MessagesUiState
|
object Loading : MessagesUiState
|
||||||
data class Success(
|
data class Success(
|
||||||
val roomTitle: String,
|
val roomTitle: String,
|
||||||
val partnerStatus: UserStatus,
|
val partnerStatus: UserStatus,
|
||||||
val partnerAvatarUrl: Any?,
|
val partnerAvatarUrl: Any?,
|
||||||
val messages: List<Message>
|
val messages: List<Message>,
|
||||||
|
val currentUserId: String
|
||||||
) : MessagesUiState
|
) : MessagesUiState
|
||||||
|
|
||||||
data class Error(val message: String) : MessagesUiState
|
data class Error(val message: String) : MessagesUiState
|
||||||
|
|
@ -34,9 +44,14 @@ class ChatViewModel @Inject constructor(
|
||||||
private val repository: ChatRoomsRepository
|
private val repository: ChatRoomsRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
//UI state var
|
||||||
private val _uiState = MutableStateFlow<MessagesUiState>(MessagesUiState.Loading)
|
private val _uiState = MutableStateFlow<MessagesUiState>(MessagesUiState.Loading)
|
||||||
val uiState: StateFlow<MessagesUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<MessagesUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
//message var
|
||||||
|
private val _sendMessageState = MutableStateFlow<SendMessageState>(SendMessageState.Idle)
|
||||||
|
val sendMessageState: StateFlow<SendMessageState> = _sendMessageState
|
||||||
|
|
||||||
fun loadMessages(roomId: String) {
|
fun loadMessages(roomId: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.value = MessagesUiState.Loading
|
_uiState.value = MessagesUiState.Loading
|
||||||
|
|
@ -62,11 +77,15 @@ class ChatViewModel @Inject constructor(
|
||||||
Triple("聊天室", UserStatus.UNKNOWN, R.drawable.avatar_v1)
|
Triple("聊天室", UserStatus.UNKNOWN, R.drawable.avatar_v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val currentLoggedInUserId = "user_123"
|
||||||
|
|
||||||
_uiState.value = MessagesUiState.Success(
|
_uiState.value = MessagesUiState.Success(
|
||||||
roomTitle = title,
|
roomTitle = title,
|
||||||
partnerStatus = status,
|
partnerStatus = status,
|
||||||
partnerAvatarUrl = avatarUrl, // ✨ 這裡就會是完整的網址或 R.drawable.avatar_v1
|
partnerAvatarUrl = avatarUrl, //這裡就會是完整的網址或 R.drawable.avatar_v1
|
||||||
messages = messagesResponse.messages
|
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
|
@HiltViewModel
|
||||||
|
|
@ -34,10 +28,6 @@ class ChatRoomsViewModel @Inject constructor(
|
||||||
private val _roomsState = MutableStateFlow<List<ChatRoom>>(emptyList())
|
private val _roomsState = MutableStateFlow<List<ChatRoom>>(emptyList())
|
||||||
val roomsState: StateFlow<List<ChatRoom>> = _roomsState
|
val roomsState: StateFlow<List<ChatRoom>> = _roomsState
|
||||||
|
|
||||||
// 1. 新增:發送訊息的 UI 狀態 Flow
|
|
||||||
private val _sendMessageState = MutableStateFlow<SendMessageState>(SendMessageState.Idle)
|
|
||||||
val sendMessageState: StateFlow<SendMessageState> = _sendMessageState
|
|
||||||
|
|
||||||
fun loadRooms() {
|
fun loadRooms() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val result = repository.fetchRooms()
|
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) {
|
enum class UserStatus(val color: Color) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue