dm-19
This commit is contained in:
parent
5ba40c2b3b
commit
9644fce272
5 changed files with 125 additions and 45 deletions
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,19 +34,40 @@ 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()!!
|
||||||
.onFailure { exception ->
|
|
||||||
|
|
||||||
_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 ?: "未知錯誤")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue