dm-111
This commit is contained in:
parent
d22d38b8fd
commit
6d48ad74ef
3 changed files with 342 additions and 313 deletions
|
|
@ -7,6 +7,8 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -105,6 +107,8 @@ fun MessageMessaging(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||||
viewModel: ChatViewModel = hiltViewModel(),
|
viewModel: ChatViewModel = hiltViewModel(),
|
||||||
bottomBarViewModel: BottomBarViewModel
|
bottomBarViewModel: BottomBarViewModel
|
||||||
) {
|
) {
|
||||||
|
|
@ -175,172 +179,177 @@ fun MessageMessaging(
|
||||||
is MessagesUiState.Loading -> "載入中..."
|
is MessagesUiState.Loading -> "載入中..."
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
modifier = Modifier
|
with(sharedTransitionScope){
|
||||||
.fillMaxSize()
|
Scaffold(
|
||||||
.background(
|
modifier = Modifier
|
||||||
brush = Brush.verticalGradient(
|
.fillMaxSize()
|
||||||
colors = listOf
|
.background(
|
||||||
(Color(0xFFDA7029),
|
brush = Brush.verticalGradient(
|
||||||
Color(0xFF777777),
|
colors = listOf
|
||||||
Color(0xFFB34800))
|
(Color(0xFFDA7029),
|
||||||
|
Color(0xFF777777),
|
||||||
|
Color(0xFFB34800))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
val currentState = uiState
|
||||||
|
if (currentState is MessagesUiState.Success) {
|
||||||
|
UserAvatar(
|
||||||
|
avatarUrl = currentState.partnerAvatarUrl,
|
||||||
|
modifier = Modifier.size(36.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = topBarTitle)
|
||||||
|
|
||||||
|
if (currentState is MessagesUiState.Success) {
|
||||||
|
if (currentState.partnerStatus == UserStatus.ONLINE) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(10.dp)
|
||||||
|
.background(
|
||||||
|
color = UserStatus.ONLINE.color,
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 🌟 關鍵修改 1:設定半透明背景色
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
// 這裡可以用 Surface 色加上 0.6 的透明度,或者直接用自訂顏色如 Color.White.copy(0.5f)
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
),
|
},
|
||||||
containerColor = Color.Transparent,
|
bottomBar = {
|
||||||
topBar = {
|
// 👈 使用我們定義的 sealed interface 狀態機
|
||||||
TopAppBar(
|
AnimatedContent(
|
||||||
title = {
|
targetState = bottomBarState,
|
||||||
Row(
|
label = "BottomBarSwitchAnimation"
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
) { currentState ->
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
when (currentState) {
|
||||||
) {
|
is BottomBarState.Input -> {
|
||||||
val currentState = uiState
|
MessageInputBar(
|
||||||
if (currentState is MessagesUiState.Success) {
|
isLoading = sendStatus is SendMessageState.Loading,
|
||||||
UserAvatar(
|
replyingMessage = replyingMessage,
|
||||||
avatarUrl = currentState.partnerAvatarUrl,
|
onCancelReply = { viewModel.setReplyingMessage(null) },
|
||||||
modifier = Modifier.size(36.dp)
|
onSendClick = { content -> viewModel.sendMessage(roomId, content) },
|
||||||
|
onImageSelected = { uri -> viewModel.uploadAttachment(roomId, uri) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(text = topBarTitle)
|
is BottomBarState.ActionMenu -> {
|
||||||
|
val currentActionMessage = currentState.message
|
||||||
|
val currentUserId = (uiState as? MessagesUiState.Success)?.currentUserId
|
||||||
|
val isOwnMessage = currentActionMessage.senderId == currentUserId
|
||||||
|
|
||||||
if (currentState is MessagesUiState.Success) {
|
MessageActionMenuRow(
|
||||||
if (currentState.partnerStatus == UserStatus.ONLINE) {
|
message = currentActionMessage,
|
||||||
Box(
|
isOwnMessage = isOwnMessage,
|
||||||
modifier = Modifier
|
onCancel = { viewModel.clearActionMessage() },
|
||||||
.size(10.dp)
|
onReplyClick = { msg ->
|
||||||
.background(
|
viewModel.setReplyingMessage(msg)
|
||||||
color = UserStatus.ONLINE.color,
|
viewModel.clearActionMessage()
|
||||||
shape = CircleShape
|
},
|
||||||
)
|
onDeleteClick = { msg ->
|
||||||
)
|
// 🌟 核心防呆:如果訊息還在發送中(SENDING),禁止點擊刪除,避免 tempId 送給後端造成 404
|
||||||
}
|
if (msg.status == MessageStatus.SENDING) {
|
||||||
|
// 可以選擇在這裡彈出一個短暫的 Toast 提示使用者
|
||||||
|
Toast.makeText(context, "訊息發送中,請稍後再操作", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
// 正常訊息,呼叫樂觀更新刪除
|
||||||
|
viewModel.deleteMessage(roomId, msg.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
// 🌟 關鍵修改 1:設定半透明背景色
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
|
||||||
// 這裡可以用 Surface 色加上 0.6 的透明度,或者直接用自訂顏色如 Color.White.copy(0.5f)
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.6f)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
bottomBar = {
|
|
||||||
// 👈 使用我們定義的 sealed interface 狀態機
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = bottomBarState,
|
|
||||||
label = "BottomBarSwitchAnimation"
|
|
||||||
) { currentState ->
|
|
||||||
when (currentState) {
|
|
||||||
is BottomBarState.Input -> {
|
|
||||||
MessageInputBar(
|
|
||||||
isLoading = sendStatus is SendMessageState.Loading,
|
|
||||||
replyingMessage = replyingMessage,
|
|
||||||
onCancelReply = { viewModel.setReplyingMessage(null) },
|
|
||||||
onSendClick = { content -> viewModel.sendMessage(roomId, content) },
|
|
||||||
onImageSelected = { uri -> viewModel.uploadAttachment(roomId, uri) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is BottomBarState.ActionMenu -> {
|
is BottomBarState.EmojiMenu -> {
|
||||||
val currentActionMessage = currentState.message
|
// 👈 渲染全新設計的獨立 Emoji Bar
|
||||||
val currentUserId = (uiState as? MessagesUiState.Success)?.currentUserId
|
val currentEmojiMessage = currentState.message
|
||||||
val isOwnMessage = currentActionMessage.senderId == currentUserId
|
MessageEmojiBar(
|
||||||
|
// 🌟 移除了 commonEmojis 參數,變得非常清爽!
|
||||||
MessageActionMenuRow(
|
onEmojiClick = { emoji ->
|
||||||
message = currentActionMessage,
|
val targetReaction = currentEmojiMessage.reactions?.find { it.emoji == emoji }
|
||||||
isOwnMessage = isOwnMessage,
|
if (targetReaction?.meReacted == true) {
|
||||||
onCancel = { viewModel.clearActionMessage() },
|
viewModel.removeMessageReaction(roomId, currentEmojiMessage.id, emoji)
|
||||||
onReplyClick = { msg ->
|
} else {
|
||||||
viewModel.setReplyingMessage(msg)
|
viewModel.addMessageReaction(roomId, currentEmojiMessage.id, emoji)
|
||||||
viewModel.clearActionMessage()
|
}
|
||||||
},
|
emojiTargetMessage = null // 點完後自動關閉
|
||||||
onDeleteClick = { msg ->
|
},
|
||||||
// 🌟 核心防呆:如果訊息還在發送中(SENDING),禁止點擊刪除,避免 tempId 送給後端造成 404
|
onCancel = {
|
||||||
if (msg.status == MessageStatus.SENDING) {
|
emojiTargetMessage = null // 點擊關閉
|
||||||
// 可以選擇在這裡彈出一個短暫的 Toast 提示使用者
|
|
||||||
Toast.makeText(context, "訊息發送中,請稍後再操作", Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
// 正常訊息,呼叫樂觀更新刪除
|
|
||||||
viewModel.deleteMessage(roomId, msg.id)
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is BottomBarState.EmojiMenu -> {
|
|
||||||
// 👈 渲染全新設計的獨立 Emoji Bar
|
|
||||||
val currentEmojiMessage = currentState.message
|
|
||||||
MessageEmojiBar(
|
|
||||||
// 🌟 移除了 commonEmojis 參數,變得非常清爽!
|
|
||||||
onEmojiClick = { emoji ->
|
|
||||||
val targetReaction = currentEmojiMessage.reactions?.find { it.emoji == emoji }
|
|
||||||
if (targetReaction?.meReacted == true) {
|
|
||||||
viewModel.removeMessageReaction(roomId, currentEmojiMessage.id, emoji)
|
|
||||||
} else {
|
|
||||||
viewModel.addMessageReaction(roomId, currentEmojiMessage.id, emoji)
|
|
||||||
}
|
|
||||||
emojiTargetMessage = null // 點完後自動關閉
|
|
||||||
},
|
|
||||||
onCancel = {
|
|
||||||
emojiTargetMessage = null // 點擊關閉
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
) { innerPadding ->
|
||||||
) { innerPadding ->
|
Box(
|
||||||
Box(
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxSize()
|
||||||
.fillMaxSize()
|
.padding(innerPadding),
|
||||||
.padding(innerPadding)
|
contentAlignment = Alignment.Center
|
||||||
,
|
) {
|
||||||
contentAlignment = Alignment.Center
|
// 根據不同的狀態渲染主畫面內容
|
||||||
) {
|
when (val state = uiState) {
|
||||||
// 根據不同的狀態渲染主畫面內容
|
is MessagesUiState.Loading -> {
|
||||||
when (val state = uiState) {
|
CircularProgressIndicator()
|
||||||
is MessagesUiState.Loading -> {
|
}
|
||||||
CircularProgressIndicator()
|
|
||||||
}
|
is MessagesUiState.Success -> {
|
||||||
is MessagesUiState.Success -> {
|
if (state.messages.isEmpty()) {
|
||||||
if (state.messages.isEmpty()) {
|
Text("目前沒有新訊息")
|
||||||
Text("目前沒有新訊息")
|
} else {
|
||||||
} else {
|
MessageList(
|
||||||
MessageList(
|
currentUserId = state.currentUserId,
|
||||||
currentUserId = state.currentUserId,
|
messages = state.messages,
|
||||||
messages = state.messages,
|
partnerAvatarUrl = state.partnerAvatarUrl,
|
||||||
partnerAvatarUrl = state.partnerAvatarUrl,
|
partnerDisplayName = state.roomTitle,
|
||||||
partnerDisplayName = state.roomTitle,
|
activeEmojiMessageId = emojiTargetMessage?.id, // ✨ 新增短按目標 ID
|
||||||
activeEmojiMessageId = emojiTargetMessage?.id, // ✨ 新增短按目標 ID
|
activeActionMessageId = actionMessage?.id,
|
||||||
activeActionMessageId = actionMessage?.id,
|
onReplyClick = { message ->
|
||||||
onReplyClick = { message ->
|
emojiTargetMessage = null
|
||||||
emojiTargetMessage = null
|
viewModel.setActionMessage(message)
|
||||||
viewModel.setActionMessage(message)
|
},
|
||||||
},
|
// 💡 單擊事件:開啟 Emoji 選單,同時清空長按選單
|
||||||
// 💡 單擊事件:開啟 Emoji 選單,同時清空長按選單
|
onRowClick = { message ->
|
||||||
onRowClick = { message ->
|
viewModel.clearActionMessage()
|
||||||
viewModel.clearActionMessage()
|
emojiTargetMessage = message
|
||||||
emojiTargetMessage = message
|
},
|
||||||
},
|
onReactionClick = { message, emoji ->
|
||||||
onReactionClick = { message, emoji ->
|
// 這是訊息氣泡下方既有 Reaction 小標籤的點擊事件,保持原樣即可
|
||||||
// 這是訊息氣泡下方既有 Reaction 小標籤的點擊事件,保持原樣即可
|
val targetReaction =
|
||||||
val targetReaction = message.reactions?.find { it.emoji == emoji }
|
message.reactions?.find { it.emoji == emoji }
|
||||||
if (targetReaction?.meReacted == true) {
|
if (targetReaction?.meReacted == true) {
|
||||||
viewModel.removeMessageReaction(roomId, message.id, emoji)
|
viewModel.removeMessageReaction(roomId, message.id, emoji)
|
||||||
} else {
|
} else {
|
||||||
viewModel.addMessageReaction(roomId, message.id, emoji)
|
viewModel.addMessageReaction(roomId, message.id, emoji)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MessagesUiState.Error -> {
|
||||||
|
Text(
|
||||||
|
text = "載入失敗:${state.message}",
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MessagesUiState.Error -> {
|
|
||||||
Text(
|
|
||||||
text = "載入失敗:${state.message}",
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.example.login_v3.home.Message.UI.Detail
|
package com.example.login_v3.home.Message.UI.Detail
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -76,6 +78,8 @@ val ChatRoom?.displayTitle: String
|
||||||
fun Loaded_Tg_Message(
|
fun Loaded_Tg_Message(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
viewModel: ChatRoomsViewModel = hiltViewModel(),
|
viewModel: ChatRoomsViewModel = hiltViewModel(),
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||||
onRoomClick: (String, String) -> Unit = { _, _ -> }
|
onRoomClick: (String, String) -> Unit = { _, _ -> }
|
||||||
) {
|
) {
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
@ -191,11 +195,13 @@ fun Loaded_Tg_Message(
|
||||||
LazyColumn(modifier = Modifier.padding(innerPadding)) {
|
LazyColumn(modifier = Modifier.padding(innerPadding)) {
|
||||||
items(
|
items(
|
||||||
items = rooms,
|
items = rooms,
|
||||||
key = { it.roomId } // 增加效能,防止列表閃爍
|
key = { it.roomId }
|
||||||
) { room ->
|
) { room ->
|
||||||
RoomItem(
|
RoomItem(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
room = room,
|
room = room,
|
||||||
|
sharedTransitionScope = sharedTransitionScope, // 傳下去
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope, // 傳下去
|
||||||
onClick = { roomId, roomName ->
|
onClick = { roomId, roomName ->
|
||||||
onRoomClick(roomId, roomName)
|
onRoomClick(roomId, roomName)
|
||||||
}
|
}
|
||||||
|
|
@ -215,142 +221,149 @@ fun Loaded_Tg_Message(
|
||||||
fun RoomItem(
|
fun RoomItem(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
room: ChatRoom,
|
room: ChatRoom,
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||||
// 🚀 修正 2:onClick 改成攜帶兩個參數 (roomId, roomName)
|
// 🚀 修正 2:onClick 改成攜帶兩個參數 (roomId, roomName)
|
||||||
onClick: (String, String) -> Unit
|
onClick: (String, String) -> Unit
|
||||||
) {
|
) {
|
||||||
val title = room.displayTitle
|
val title = room.displayTitle
|
||||||
|
|
||||||
Card(
|
with(sharedTransitionScope){
|
||||||
modifier = Modifier
|
Card(
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 12.dp, vertical = 6.dp) // 增加外邊距,讓 Card 之間有呼吸感
|
|
||||||
.clickable {
|
|
||||||
// 🚀 修正:直接呼叫傳進來的 lambda 函式即可,不要包含 createRoute 唷!
|
|
||||||
onClick(room.roomId, room.displayTitle)
|
|
||||||
}
|
|
||||||
,
|
|
||||||
shape = RoundedCornerShape(16.dp), // 較大的圓角看起來更現代
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = Color.Transparent // ✅ 透明背景
|
|
||||||
),
|
|
||||||
elevation = CardDefaults.cardElevation(
|
|
||||||
defaultElevation = 0.dp // 增加輕微陰影
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
){
|
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||||
//glass effect
|
.clickable { onClick(room.roomId, room.displayTitle) }
|
||||||
|
// 🌟 關鍵:把共享元素鎖定在整個外層 Container 上!
|
||||||
|
.sharedElement(
|
||||||
|
rememberSharedContentState(key = "container_${room.roomId}"),
|
||||||
|
animatedVisibilityScope = animatedVisibilityScope
|
||||||
|
)
|
||||||
|
,
|
||||||
|
shape = RoundedCornerShape(16.dp), // 較大的圓角看起來更現代
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color.Transparent // ✅ 透明背景
|
||||||
|
),
|
||||||
|
elevation = CardDefaults.cardElevation(
|
||||||
|
defaultElevation = 0.dp // 增加輕微陰影
|
||||||
|
)
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.matchParentSize()
|
.fillMaxSize()
|
||||||
.blur(10.dp)
|
){
|
||||||
)
|
//glass effect
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
|
||||||
.matchParentSize()
|
|
||||||
.background(
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
RoundedCornerShape(16.dp)
|
|
||||||
)
|
|
||||||
.border(
|
|
||||||
1.dp,
|
|
||||||
Color.White.copy(alpha = 0.3f),
|
|
||||||
RoundedCornerShape(16.dp)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.padding(12.dp),
|
|
||||||
) {
|
|
||||||
// 1. 頭像
|
|
||||||
val imageUrl = room.roomIconUrl ?: room.partner?.fullContactAvatarUrl
|
|
||||||
AsyncImage(
|
|
||||||
model = imageUrl,
|
|
||||||
contentDescription = "Avatar",
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(56.dp) // 稍微加大一點
|
.matchParentSize()
|
||||||
.clip(CircleShape)
|
.blur(10.dp)
|
||||||
.border(1.dp, Color.White, CircleShape) // 頭像加個細白邊
|
)
|
||||||
.background(Color.LightGray)
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
Color.White.copy(alpha = 0.3f),
|
||||||
|
RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
|
|
||||||
// 2. 中間內容
|
Row(
|
||||||
Column(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.padding(12.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
// 1. 頭像
|
||||||
text = room.roomName.orEmpty().ifEmpty {
|
val imageUrl = room.roomIconUrl ?: room.partner?.fullContactAvatarUrl
|
||||||
room.partner?.displayName ?: "Unknown"
|
AsyncImage(
|
||||||
},
|
model = imageUrl,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
contentDescription = "Avatar",
|
||||||
fontWeight = FontWeight.Bold,
|
contentScale = ContentScale.Crop,
|
||||||
color = Color(0xFF333333),
|
modifier = Modifier
|
||||||
maxLines = 1,
|
.size(56.dp) // 稍微加大一點
|
||||||
overflow = TextOverflow.Ellipsis
|
.clip(CircleShape)
|
||||||
|
.border(1.dp, Color.White, CircleShape) // 頭像加個細白邊
|
||||||
|
.background(Color.LightGray)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
val lastMsgText = when {
|
// 2. 中間內容
|
||||||
room.lastMessage?.isDeleted == true -> "訊息已刪除"
|
Column(
|
||||||
room.lastMessage?.content != null -> room.lastMessage.content
|
modifier = Modifier.weight(1f)
|
||||||
room.lastMessage?.attachment != null -> "[檔案] ${room.lastMessage.attachment.filename}"
|
) {
|
||||||
else -> "尚未有訊息"
|
|
||||||
}
|
|
||||||
|
|
||||||
Row() {
|
|
||||||
Text(
|
Text(
|
||||||
text = lastMsgText,
|
text = room.roomName.orEmpty().ifEmpty {
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
room.partner?.displayName ?: "Unknown"
|
||||||
color = Color.Gray,
|
},
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color(0xFF333333),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(6.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
if (room.unreadCount > 0) {
|
val lastMsgText = when {
|
||||||
Badge(
|
room.lastMessage?.isDeleted == true -> "訊息已刪除"
|
||||||
containerColor = Color(0xFFDA7029), // 使用你的主橘色
|
room.lastMessage?.content != null -> room.lastMessage.content
|
||||||
contentColor = Color.White
|
room.lastMessage?.attachment != null -> "[檔案] ${room.lastMessage.attachment.filename}"
|
||||||
) {
|
else -> "尚未有訊息"
|
||||||
Text(
|
}
|
||||||
text = if (room.unreadCount > 99) "99+" else room.unreadCount.toString(),
|
|
||||||
modifier = Modifier.padding(horizontal = 4.dp)
|
Row() {
|
||||||
)
|
Text(
|
||||||
|
text = lastMsgText,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = Color.Gray,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
|
||||||
|
if (room.unreadCount > 0) {
|
||||||
|
Badge(
|
||||||
|
containerColor = Color(0xFFDA7029), // 使用你的主橘色
|
||||||
|
contentColor = Color.White
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (room.unreadCount > 99) "99+" else room.unreadCount.toString(),
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//online status
|
||||||
|
val partner = room.partner // 先從 room 取得 partner
|
||||||
|
if (partner != null) {
|
||||||
|
val status = UserStatus.fromString(partner.status)
|
||||||
|
if (status != UserStatus.UNKNOWN) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(14.dp)
|
||||||
|
.background(status.color, CircleShape) // 直接設定圓形背景
|
||||||
|
.border(2.dp, Color.White, CircleShape) // 白色外圈邊線
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(2.dp))
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//online status
|
|
||||||
val partner = room.partner // 先從 room 取得 partner
|
|
||||||
if (partner != null) {
|
|
||||||
val status = UserStatus.fromString(partner.status)
|
|
||||||
if (status != UserStatus.UNKNOWN) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(14.dp)
|
|
||||||
.background(status.color, CircleShape) // 直接設定圓形背景
|
|
||||||
.border(2.dp, Color.White, CircleShape) // 白色外圈邊線
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.example.login_v3.home.Message.UI
|
package com.example.login_v3.home.Message.UI
|
||||||
|
|
||||||
|
import androidx.compose.animation.SharedTransitionLayout
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
|
|
@ -33,66 +34,72 @@ fun Tg_Message(
|
||||||
) {
|
) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
NavHost(
|
SharedTransitionLayout{
|
||||||
navController = navController,
|
NavHost(
|
||||||
startDestination = Screen.Messages.route,
|
navController = navController,
|
||||||
) {
|
startDestination = Screen.Messages.route,
|
||||||
// 你的訊息主頁面
|
) {
|
||||||
composable(Screen.Messages.route) {
|
// 你的訊息主頁面
|
||||||
Loaded_Tg_Message(
|
// 訊息主頁面
|
||||||
navController = navController,
|
composable(Screen.Messages.route) {
|
||||||
// ✨ 2. 這裡的點擊事件如果原本有傳 roomName,現在直接忽略,只傳 roomId 給路由
|
Loaded_Tg_Message(
|
||||||
onRoomClick = { roomId, _ ->
|
navController = navController,
|
||||||
val route = Screen.MessageMessaging.createRoute(roomId)
|
// 2. 傳入 sharedTransitionScope (this@SharedTransitionLayout)
|
||||||
navController.navigate(route)
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
}
|
// 3. 傳入 animatedVisibilityScope (this@composable)
|
||||||
)
|
animatedVisibilityScope = this@composable,
|
||||||
}
|
onRoomClick = { roomId, _ ->
|
||||||
|
val route = Screen.MessageMessaging.createRoute(roomId)
|
||||||
|
navController.navigate(route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 新建聯絡頁面
|
// 新建聯絡頁面
|
||||||
composable(Screen.CreateContact.route) {
|
composable(Screen.CreateContact.route) {
|
||||||
Message_add_contact(navController)
|
Message_add_contact(navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 好友列表頁面
|
// 好友列表頁面
|
||||||
composable(Screen.FriendsList.route) {
|
composable(Screen.FriendsList.route) {
|
||||||
Message_contact_list(
|
Message_contact_list(
|
||||||
navController,
|
navController,
|
||||||
bottomBarViewModel = bottomBarViewModel
|
bottomBarViewModel = bottomBarViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//QR code
|
//QR code
|
||||||
composable(Screen.ScanQRcode.route) {
|
composable(Screen.ScanQRcode.route) {
|
||||||
Scan_QRcode(
|
Scan_QRcode(
|
||||||
navController,
|
navController,
|
||||||
bottomBarViewModel = bottomBarViewModel
|
bottomBarViewModel = bottomBarViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Message detail
|
//Message detail
|
||||||
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 = ""
|
}
|
||||||
}
|
)
|
||||||
)
|
) { backStackEntry ->
|
||||||
) { backStackEntry ->
|
val roomId = backStackEntry.arguments?.getString("roomId").orEmpty()
|
||||||
val roomId = backStackEntry.arguments?.getString("roomId").orEmpty()
|
|
||||||
|
MessageMessaging(
|
||||||
|
roomId = roomId,
|
||||||
|
navController = navController,
|
||||||
|
// 4. 同樣把這兩個 Scope 傳入詳情頁
|
||||||
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
|
animatedVisibilityScope = this@composable,
|
||||||
|
onBackClick = { navController.popBackStack() },
|
||||||
|
bottomBarViewModel = bottomBarViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ✨ 4. 呼叫新版的 MessageMessaging,不再傳入 roomName 參數
|
|
||||||
MessageMessaging(
|
|
||||||
roomId = roomId,
|
|
||||||
navController = navController,
|
|
||||||
onBackClick = {
|
|
||||||
navController.popBackStack()
|
|
||||||
},
|
|
||||||
bottomBarViewModel = bottomBarViewModel
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue