add delta sync for dm-v17

This commit is contained in:
eric 2026-08-07 16:03:50 +08:00
parent fc605183d5
commit d84ba852c6

View file

@ -85,11 +85,14 @@ import androidx.compose.foundation.layout.statusBars // 如果要使用 statusBa
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowDownward
import androidx.compose.material.icons.filled.Block
import androidx.compose.material.icons.filled.Call import androidx.compose.material.icons.filled.Call
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Wallpaper
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.example.login_v3.home.Message.UI.Detail.Message_Component.AudioMessageBubble import com.example.login_v3.home.Message.UI.Detail.Message_Component.AudioMessageBubble
@ -112,6 +115,12 @@ sealed interface BottomBarState {
object AttachmentMenu : BottomBarState // 🎯 新增:附件選單 object AttachmentMenu : BottomBarState // 🎯 新增:附件選單
} }
// 🎯 新增:Top Bar 狀態機
sealed interface TopBarState {
object Regular : TopBarState // 預設:姓名、頭像、通話、更多
object MoreOptions : TopBarState // 點擊「更多」後:顯示功能工具列
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MessageMessaging( fun MessageMessaging(
@ -143,6 +152,9 @@ fun MessageMessaging(
//inspect video //inspect video
var lightboxVideoUrl by remember { mutableStateOf<String?>(null) } var lightboxVideoUrl by remember { mutableStateOf<String?>(null) }
// 🎯 新增:Top Bar 的本地狀態
var topBarState by remember { mutableStateOf<TopBarState>(TopBarState.Regular) }
// 🌟 核心:使用 derivedStateOf 統一控管狀態權限(長按優先權通常大於單擊) // 🌟 核心:使用 derivedStateOf 統一控管狀態權限(長按優先權通常大於單擊)
// 🎯 新增:用來控管非訊息觸發的底部狀態(例如:AttachmentMenu) // 🎯 新增:用來控管非訊息觸發的底部狀態(例如:AttachmentMenu)
var localBottomBarState by remember { mutableStateOf<BottomBarState?>(null) } var localBottomBarState by remember { mutableStateOf<BottomBarState?>(null) }
@ -256,130 +268,224 @@ fun MessageMessaging(
val topBarShape = RoundedCornerShape(16.dp) val topBarShape = RoundedCornerShape(16.dp)
Row( // 👈 使用 AnimatedContent 讓 TopBar 切換更平滑
modifier = Modifier AnimatedContent(
.fillMaxWidth() targetState = topBarState,
.padding(horizontal = 4.dp) label = "TopBarSwitchAnimation"
, ) { currentState ->
verticalAlignment = Alignment.CenterVertically, when (currentState) {
horizontalArrangement = Arrangement.Start TopBarState.Regular -> {
) { Row(
Spacer(modifier = Modifier.width(6.dp)) modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Spacer(modifier = Modifier.width(6.dp))
//name and avatar //name and avatar
Box( Box(
modifier = Modifier modifier = Modifier
.weight(0.68f) .weight(0.68f)
.sharedElement( .sharedElement(
rememberSharedContentState(key = "container_$roomId"), rememberSharedContentState(key = "container_$roomId"),
animatedVisibilityScope = animatedVisibilityScope animatedVisibilityScope = animatedVisibilityScope
) )
.clip(topBarShape) .clip(topBarShape)
.background( .background(
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
shape = topBarShape shape = topBarShape
) )
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(58.dp) // 58dp 就會是純內容的高度,內容就會完美垂直置中了! .height(58.dp) // 58dp 就會是純內容的高度,內容就會完美垂直置中了!
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
) { ) {
val currentState = uiState val uiStateVal = uiState
if (currentState is MessagesUiState.Success) { if (uiStateVal is MessagesUiState.Success) {
UserAvatar( UserAvatar(
avatarUrl = currentState.partnerAvatarUrl, avatarUrl = uiStateVal.partnerAvatarUrl,
modifier = Modifier.size(42.dp) modifier = Modifier.size(42.dp)
) )
}
Spacer(modifier = Modifier.width(12.dp))
Text(
text = topBarTitle,
style = MaterialTheme.typography.titleMedium,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
if (uiStateVal is MessagesUiState.Success) {
// 拿掉原本只判斷 ONLINE 的 if,讓所有狀態(除了 UNKNOWN)都能顯示
UserStatusDot(
status = uiStateVal.partnerStatus,
size = 14.dp // 建議統一成 14.dp,包含白色外圈的視覺效果最好
)
}
Spacer(modifier = Modifier.width(12.dp))
}
}
Spacer(modifier = Modifier.width(8.dp))
//voice call button
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.weight(0.16f)
.height(58.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = topBarShape
)
) {
IconButton(
onClick = {
// 這裡觸發語音通話的邏輯,例如:viewModel.startVoiceCall(roomId)
},
modifier = Modifier
.size(48.dp) // 給予標準的點擊區域大小
) {
Icon(
imageVector = Icons.Default.Call, // 👈 這裡使用 Material Design 的預設電話圖標,也可以換成 Icons.Rounded.Phone
contentDescription = "語音通話",
tint = Color.White, // 👈 顏色可以根據你的漸層背景調整,如果是亮色背景可改用 MaterialTheme.colorScheme.onBackground
modifier = Modifier.size(28.dp) // Icon 實際的大小
)
}
}
Spacer(modifier = Modifier.width(8.dp))
//more option button
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.weight(0.16f)
.height(58.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = topBarShape
)
) {
IconButton(
onClick = {
topBarState = TopBarState.MoreOptions
},
modifier = Modifier
.size(48.dp) // 給予標準的點擊區域大小
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "更多選項",
tint = Color.White,
modifier = Modifier.size(28.dp)
)
}
}
Spacer(modifier = Modifier.width(6.dp))
} }
}
Spacer(modifier = Modifier.width(12.dp)) TopBarState.MoreOptions -> {
// 🎯 當切換到「更多選項」時顯示的 TopBar
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Spacer(modifier = Modifier.width(6.dp))
Text( // 關閉按鈕,點擊回到 Regular 狀態
text = topBarTitle, Box(
style = MaterialTheme.typography.titleMedium, contentAlignment = Alignment.Center,
fontSize = 24.sp, modifier = Modifier
fontWeight = FontWeight.Bold, .size(58.dp)
color = MaterialTheme.colorScheme.onBackground, .background(
maxLines = 1, color = MaterialTheme.colorScheme.primary,
overflow = TextOverflow.Ellipsis, shape = topBarShape
modifier = Modifier.weight(1f) )
) ) {
IconButton(onClick = { topBarState = TopBarState.Regular }) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "關閉更多選項",
tint = Color.White,
modifier = Modifier.size(28.dp)
)
}
}
Spacer(modifier = Modifier.width(8.dp))
if (currentState is MessagesUiState.Success) { // 更多選項的內容(這裡可以放置搜尋、靜音、成員清單等功能)
// 拿掉原本只判斷 ONLINE 的 if,讓所有狀態(除了 UNKNOWN)都能顯示 Box(
UserStatusDot( modifier = Modifier
status = currentState.partnerStatus, .weight(1f)
size = 14.dp // 建議統一成 14.dp,包含白色外圈的視覺效果最好 .height(58.dp)
) .background(
color = MaterialTheme.colorScheme.primary,
shape = topBarShape
),
contentAlignment = Alignment.Center
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth()
) {
// 搜尋訊息
IconButton(onClick = { /* TODO */ }) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "搜尋訊息",
tint = Color.White,
modifier = Modifier.size(26.dp)
)
}
// 更換壁紙
IconButton(onClick = { /* TODO */ }) {
Icon(
imageVector = Icons.Default.Wallpaper,
contentDescription = "更換壁紙",
tint = Color.White,
modifier = Modifier.size(26.dp)
)
}
// 封鎖用戶
IconButton(onClick = { /* TODO */ }) {
Icon(
imageVector = Icons.Default.Block,
contentDescription = "封鎖用戶",
tint = Color.White,
modifier = Modifier.size(26.dp)
)
}
}
}
Spacer(modifier = Modifier.width(6.dp))
} }
Spacer(modifier = Modifier.width(12.dp))
} }
} }
Spacer(modifier = Modifier.width(8.dp))
//voice call button
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.weight(0.16f)
.height(58.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = topBarShape
)
) {
IconButton(
onClick = {
// 這裡觸發語音通話的邏輯,例如:viewModel.startVoiceCall(roomId)
},
modifier = Modifier
.size(48.dp) // 給予標準的點擊區域大小
) {
Icon(
imageVector = Icons.Default.Call, // 👈 這裡使用 Material Design 的預設電話圖標,也可以換成 Icons.Rounded.Phone
contentDescription = "語音通話",
tint = Color.White, // 👈 顏色可以根據你的漸層背景調整,如果是亮色背景可改用 MaterialTheme.colorScheme.onBackground
modifier = Modifier.size(28.dp) // Icon 實際的大小
)
}
}
Spacer(modifier = Modifier.width(8.dp))
//more option button
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.weight(0.16f)
.height(58.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = topBarShape
)
) {
IconButton(
onClick = {
// 這裡觸發語音通話的邏輯,例如:viewModel.startVoiceCall(roomId)
},
modifier = Modifier
.size(48.dp) // 給予標準的點擊區域大小
) {
Icon(
imageVector = Icons.Default.MoreVert, // 👈 這裡使用 Material Design 的預設電話圖標,也可以換成 Icons.Rounded.Phone
contentDescription = "語音通話",
tint = Color.White, // 👈 顏色可以根據你的漸層背景調整,如果是亮色背景可改用 MaterialTheme.colorScheme.onBackground
modifier = Modifier.size(28.dp) // Icon 實際的大小
)
}
}
Spacer(modifier = Modifier.width(6.dp))
} }
Spacer(modifier = Modifier.height(6.dp)) Spacer(modifier = Modifier.height(6.dp))