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 package com.example.login_v3.data.repository.dm
import com.example.login_v3.data.api.TecnologiaApi 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.MessageResponse
import com.example.login_v3.data.api.api_class.RoomListResponse import com.example.login_v3.data.api.api_class.RoomListResponse
import javax.inject.Inject import javax.inject.Inject
@ -48,4 +49,19 @@ class ChatRoomsRepository @Inject constructor(
Result.failure(e) 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape 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.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -30,18 +36,17 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
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.UserStatus
import com.example.login_v3.navigation.Screen import com.example.login_v3.navigation.Screen
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MessageMessaging( fun MessageMessaging(
roomId: String, roomId: String,
roomName: String,
navController: NavController, navController: NavController,
onBackClick: () -> Unit, onBackClick: () -> Unit,
viewModel: ChatViewModel = hiltViewModel() viewModel: ChatViewModel = hiltViewModel()
@ -51,12 +56,47 @@ fun MessageMessaging(
LaunchedEffect(roomId) { LaunchedEffect(roomId) {
viewModel.loadMessages(roomId) viewModel.loadMessages(roomId)
} }
// 根據目前的 uiState 來動態決定 TopBar 要顯示什麼文字
val topBarTitle = when (val state = uiState) {
is MessagesUiState.Success -> state.roomTitle
is MessagesUiState.Error -> "載入失敗"
is MessagesUiState.Loading -> "載入中..."
}
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
//need to be sender id title = {
title = { Text(roomName) }, 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 -> ) { innerPadding ->
@ -66,6 +106,7 @@ fun MessageMessaging(
.padding(innerPadding), .padding(innerPadding),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
// 根據不同的狀態渲染主畫面內容
when (val state = uiState) { when (val state = uiState) {
is MessagesUiState.Loading -> { is MessagesUiState.Loading -> {
CircularProgressIndicator() CircularProgressIndicator()
@ -74,6 +115,7 @@ fun MessageMessaging(
if (state.messages.isEmpty()) { if (state.messages.isEmpty()) {
Text("目前沒有新訊息") Text("目前沒有新訊息")
} else { } else {
// ⚠️ 記得把原來的 state.messages 傳進去
MessageList(messages = 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.Message_contact_list
import com.example.login_v3.home.Message.UI.Detail.Scan_QRcode 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.ChatRoomsViewModel
import com.example.login_v3.home.Message.ViewModel.UserStatus
import com.example.login_v3.navigation.BottomBarViewModel import com.example.login_v3.navigation.BottomBarViewModel
@ -87,9 +88,9 @@ sealed class Screen(val route: String) {
object CreateContact : Screen("create_contact") object CreateContact : Screen("create_contact")
object FriendsList : Screen("friends_list") object FriendsList : Screen("friends_list")
object ScanQRcode : Screen("scan_QRcode") object ScanQRcode : Screen("scan_QRcode")
object MessageMessaging : Screen("message_messaging?roomId={roomId}&roomName={roomName}") { object MessageMessaging : Screen("message_messaging?roomId={roomId}") {
fun createRoute(roomId: String, roomName: String): String { fun createRoute(roomId: String): String {
return "message_messaging?roomId=$roomId&roomName=$roomName" return "message_messaging?roomId=$roomId"
} }
} }
} }
@ -108,9 +109,9 @@ fun Tg_Message(
composable(Screen.Messages.route) { composable(Screen.Messages.route) {
Loaded_Tg_Message( Loaded_Tg_Message(
navController = navController, navController = navController,
// 🚀 完美接起傳出來的兩個參數,不再發生 No value passed 的狀況! // ✨ 2. 這裡的點擊事件如果原本有傳 roomName,現在直接忽略,只傳 roomId 給路由
onRoomClick = { roomId, roomName -> onRoomClick = { roomId, _ ->
val route = Screen.MessageMessaging.createRoute(roomId, roomName) val route = Screen.MessageMessaging.createRoute(roomId)
navController.navigate(route) navController.navigate(route)
} }
) )
@ -138,26 +139,19 @@ fun Tg_Message(
composable( composable(
route = Screen.MessageMessaging.route, route = Screen.MessageMessaging.route,
arguments = listOf( arguments = listOf(
// ✨ 3. 只保留 roomId 的參數宣告,完全刪除 roomName 的 navArgument
navArgument("roomId") { navArgument("roomId") {
type = NavType.StringType type = NavType.StringType
nullable = true nullable = true
defaultValue = "" defaultValue = ""
},
// 🚀 修正:新增接收 roomName 的導航參數
navArgument("roomName") {
type = NavType.StringType
nullable = true
defaultValue = "聊天室"
} }
) )
) { backStackEntry -> ) { backStackEntry ->
val roomId = backStackEntry.arguments?.getString("roomId").orEmpty() val roomId = backStackEntry.arguments?.getString("roomId").orEmpty()
// 🚀 修正:抓取名字
val roomName = backStackEntry.arguments?.getString("roomName") ?: "聊天室"
// ✨ 4. 呼叫新版的 MessageMessaging,不再傳入 roomName 參數
MessageMessaging( MessageMessaging(
roomId = roomId, roomId = roomId,
roomName = roomName, // 🚀 傳入 UI 中
navController = navController, navController = navController,
onBackClick = { onBackClick = {
navController.popBackStack() 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 kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
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.UserStatus
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -16,7 +17,11 @@ import kotlinx.coroutines.flow.asStateFlow
sealed interface MessagesUiState { sealed interface MessagesUiState {
object Loading : 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 data class Error(val message: String) : MessagesUiState
} }
@ -29,18 +34,39 @@ class ChatViewModel @Inject constructor(
val uiState: StateFlow<MessagesUiState> = _uiState.asStateFlow() val uiState: StateFlow<MessagesUiState> = _uiState.asStateFlow()
fun loadMessages(roomId: String) { fun loadMessages(roomId: String) {
viewModelScope.launch { viewModelScope.launch {
_uiState.value = MessagesUiState.Loading _uiState.value = MessagesUiState.Loading
repository.getChatMessages(roomId) // 1. 同時獲取房間資訊與訊息列表
.onSuccess { response -> val roomResult = repository.getChatRoom(roomId) // ⚠️ 需確保 Repository 有提供此方法
val messagesResult = repository.getChatMessages(roomId)
_uiState.value = MessagesUiState.Success(messages = response.messages) if (messagesResult.isSuccess) {
val messagesResponse = messagesResult.getOrNull()!!
// 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)
} }
.onFailure { exception ->
_uiState.value = MessagesUiState.Error(message = exception.message ?: "未知錯誤") _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 package com.example.login_v3.home.Message.ViewModel
import android.util.Log import android.util.Log
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.login_v3.R 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
}
}
}
}