This commit is contained in:
Eric85710 2026-05-22 15:21:14 +08:00
parent 5ba40c2b3b
commit 9644fce272
5 changed files with 125 additions and 45 deletions

View file

@ -1,6 +1,7 @@
package com.example.login_v3.data.repository.dm
import com.example.login_v3.data.api.TecnologiaApi
import com.example.login_v3.data.api.api_class.ChatRoom
import com.example.login_v3.data.api.api_class.MessageResponse
import com.example.login_v3.data.api.api_class.RoomListResponse
import javax.inject.Inject
@ -48,4 +49,19 @@ class ChatRoomsRepository @Inject constructor(
Result.failure(e)
}
}
// 在 ChatRoomsRepository 裡面加入:
suspend fun getChatRoom(roomId: String): Result<ChatRoom?> {
return try {
// 呼叫你原本就有的 fetchRooms() 獲取所有房間
val responseResult = fetchRooms()
responseResult.map { roomListResponse ->
// 從列表裡面找出 roomId 相同的那間房間
roomListResponse.rooms.find { it.roomId == roomId }
}
} catch (e: Exception) {
Result.failure(e)
}
}
}

View file

@ -10,12 +10,18 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@ -30,18 +36,17 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
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.UserStatus
import com.example.login_v3.navigation.Screen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MessageMessaging(
roomId: String,
roomName: String,
navController: NavController,
onBackClick: () -> Unit,
viewModel: ChatViewModel = hiltViewModel()
@ -51,12 +56,47 @@ fun MessageMessaging(
LaunchedEffect(roomId) {
viewModel.loadMessages(roomId)
}
// 根據目前的 uiState 來動態決定 TopBar 要顯示什麼文字
val topBarTitle = when (val state = uiState) {
is MessagesUiState.Success -> state.roomTitle
is MessagesUiState.Error -> "載入失敗"
is MessagesUiState.Loading -> "載入中..."
}
Scaffold(
topBar = {
TopAppBar(
//need to be sender id
title = { Text(roomName) },
title = {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(text = topBarTitle)
// ✨ 如果是 Success 狀態,且對方是在線狀態(ONLINE),就顯示綠色小圓點
if (uiState is MessagesUiState.Success) {
val successState = uiState as MessagesUiState.Success
if (successState.partnerStatus == UserStatus.ONLINE) {
Box(
modifier = Modifier
.size(10.dp)
.background(
color = UserStatus.ONLINE.color,
shape = CircleShape
)
)
}
}
}
},
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "返回"
)
}
}
)
}
) { innerPadding ->
@ -66,6 +106,7 @@ fun MessageMessaging(
.padding(innerPadding),
contentAlignment = Alignment.Center
) {
// 根據不同的狀態渲染主畫面內容
when (val state = uiState) {
is MessagesUiState.Loading -> {
CircularProgressIndicator()
@ -74,6 +115,7 @@ fun MessageMessaging(
if (state.messages.isEmpty()) {
Text("目前沒有新訊息")
} else {
// ⚠️ 記得把原來的 state.messages 傳進去
MessageList(messages = state.messages)
}
}

View file

@ -79,6 +79,7 @@ import com.example.login_v3.home.Message.UI.Detail.Message_add_contact
import com.example.login_v3.home.Message.UI.Detail.Message_contact_list
import com.example.login_v3.home.Message.UI.Detail.Scan_QRcode
import com.example.login_v3.home.Message.ViewModel.ChatRoomsViewModel
import com.example.login_v3.home.Message.ViewModel.UserStatus
import com.example.login_v3.navigation.BottomBarViewModel
@ -87,9 +88,9 @@ sealed class Screen(val route: String) {
object CreateContact : Screen("create_contact")
object FriendsList : Screen("friends_list")
object ScanQRcode : Screen("scan_QRcode")
object MessageMessaging : Screen("message_messaging?roomId={roomId}&roomName={roomName}") {
fun createRoute(roomId: String, roomName: String): String {
return "message_messaging?roomId=$roomId&roomName=$roomName"
object MessageMessaging : Screen("message_messaging?roomId={roomId}") {
fun createRoute(roomId: String): String {
return "message_messaging?roomId=$roomId"
}
}
}
@ -108,9 +109,9 @@ fun Tg_Message(
composable(Screen.Messages.route) {
Loaded_Tg_Message(
navController = navController,
// 🚀 完美接起傳出來的兩個參數,不再發生 No value passed 的狀況!
onRoomClick = { roomId, roomName ->
val route = Screen.MessageMessaging.createRoute(roomId, roomName)
// ✨ 2. 這裡的點擊事件如果原本有傳 roomName,現在直接忽略,只傳 roomId 給路由
onRoomClick = { roomId, _ ->
val route = Screen.MessageMessaging.createRoute(roomId)
navController.navigate(route)
}
)
@ -138,26 +139,19 @@ fun Tg_Message(
composable(
route = Screen.MessageMessaging.route,
arguments = listOf(
// ✨ 3. 只保留 roomId 的參數宣告,完全刪除 roomName 的 navArgument
navArgument("roomId") {
type = NavType.StringType
nullable = true
defaultValue = ""
},
// 🚀 修正:新增接收 roomName 的導航參數
navArgument("roomName") {
type = NavType.StringType
nullable = true
defaultValue = "聊天室"
}
)
) { backStackEntry ->
val roomId = backStackEntry.arguments?.getString("roomId").orEmpty()
// 🚀 修正:抓取名字
val roomName = backStackEntry.arguments?.getString("roomName") ?: "聊天室"
// ✨ 4. 呼叫新版的 MessageMessaging,不再傳入 roomName 參數
MessageMessaging(
roomId = roomId,
roomName = roomName, // 🚀 傳入 UI 中
navController = navController,
onBackClick = {
navController.popBackStack()
@ -461,21 +455,3 @@ fun RoomItem(
}
}
}
enum class UserStatus(val color: Color) {
ONLINE(Color.Green),
AWAY(Color.Yellow),
OFFLINE(Color.Gray),
UNKNOWN(Color.DarkGray);
companion object {
fun fromString(status: String?): UserStatus {
return when (status?.lowercase()) {
"online" -> ONLINE
"away" -> AWAY
"offline" -> OFFLINE
else -> UNKNOWN
}
}
}
}

View file

@ -9,6 +9,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
import com.example.login_v3.data.api.api_class.Message
import com.example.login_v3.home.Message.ViewModel.UserStatus
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -16,7 +17,11 @@ import kotlinx.coroutines.flow.asStateFlow
sealed interface MessagesUiState {
object Loading : MessagesUiState
data class Success(val messages: List<Message>) : MessagesUiState
data class Success(
val roomTitle: String,
val partnerStatus: UserStatus,
val messages: List<Message>
) : MessagesUiState
data class Error(val message: String) : MessagesUiState
}
@ -29,18 +34,39 @@ class ChatViewModel @Inject constructor(
val uiState: StateFlow<MessagesUiState> = _uiState.asStateFlow()
fun loadMessages(roomId: String) {
viewModelScope.launch {
_uiState.value = MessagesUiState.Loading
repository.getChatMessages(roomId)
.onSuccess { response ->
// 1. 同時獲取房間資訊與訊息列表
val roomResult = repository.getChatRoom(roomId) // ⚠️ 需確保 Repository 有提供此方法
val messagesResult = repository.getChatMessages(roomId)
_uiState.value = MessagesUiState.Success(messages = response.messages)
}
.onFailure { exception ->
if (messagesResult.isSuccess) {
val messagesResponse = messagesResult.getOrNull()!!
_uiState.value = MessagesUiState.Error(message = exception.message ?: "未知錯誤")
// 2. 根據房間資訊決定標題邏輯
val room = roomResult.getOrNull()
val (title, status) = if (room != null) {
if (room.roomType == "DM") {
val name = room.partner?.displayName ?: room.partner?.username ?: "未知用戶"
val userStatus = UserStatus.fromString(room.partner?.status)
Pair(name, userStatus)
} else {
Pair(room.roomName ?: "未命名群組", UserStatus.UNKNOWN)
}
} else {
// 如果真的拿不到房間資訊,給個安全預設值
Pair("聊天室", UserStatus.UNKNOWN)
}
_uiState.value = MessagesUiState.Success(
roomTitle = title,
partnerStatus = status,
messages = messagesResponse.messages
)
} else {
val exception = messagesResult.exceptionOrNull()
_uiState.value = MessagesUiState.Error(message = exception?.message ?: "未知錯誤")
}
}
}

View file

@ -2,6 +2,7 @@
package com.example.login_v3.home.Message.ViewModel
import android.util.Log
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.login_v3.R
@ -41,3 +42,22 @@ class ChatRoomsViewModel @Inject constructor(
}
}
enum class UserStatus(val color: Color) {
ONLINE(Color.Green),
OFFLINE(Color.Gray),
UNKNOWN(Color.Transparent); // 或者是你原本設定的顏色
companion object {
fun fromString(status: String?): UserStatus {
if (status == null) return UNKNOWN
// 使用 try-catch 或者是 safe valueOf 確保絕對不閃退
return try {
// uppercase() 預防後端一下給大寫一下給小寫 (例如 "online" vs "ONLINE")
valueOf(status.uppercase())
} catch (e: IllegalArgumentException) {
UNKNOWN // 找不到對應的狀態就安全地回傳 UNKNOWN
}
}
}
}