dm-129
This commit is contained in:
parent
c2e81c8d7b
commit
b41e149095
1 changed files with 95 additions and 110 deletions
|
|
@ -17,14 +17,12 @@ import androidx.compose.foundation.layout.Spacer
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.filled.Warning
|
||||
|
|
@ -70,7 +68,6 @@ import com.example.login_v3.home.Message.ViewModel.Detail.DeleteMessageState
|
|||
import com.example.login_v3.home.Message.ViewModel.Detail.MessageStatus
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.MessagesUiState
|
||||
import com.example.login_v3.home.Message.ViewModel.Detail.SendMessageState
|
||||
import com.example.login_v3.home.Message.ViewModel.UserStatus
|
||||
import com.example.login_v3.navigation.BottomBarViewModel
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
|
|
@ -514,32 +511,22 @@ fun MessageRow(
|
|||
val contentLowerCase = (message.content ?: "").lowercase()
|
||||
val imageExtensions = listOf(".jpg", ".jpeg", ".png", ".webp", ".heic", ".heif")
|
||||
|
||||
// 修正:只要字串包含圖片副檔名,就認定它是舊版圖片路徑(拿掉 startsWith("http") 的限制)
|
||||
val isLegacyImage = imageExtensions.any { contentLowerCase.contains(it) }
|
||||
val isImage = isAttachmentImage || isLegacyImage
|
||||
|
||||
|
||||
val rawContent = message.content ?: ""
|
||||
|
||||
val finalImageModel = remember(message) {
|
||||
// 1. 取得原始路徑(優先使用 attachment 的 filename,次之用 content)
|
||||
val rawPath = if (isAttachmentImage && !message.attachment?.filename.isNullOrBlank()) {
|
||||
message.attachment!!.filename
|
||||
} else {
|
||||
message.content ?: ""
|
||||
}
|
||||
|
||||
val baseUrl = "https://tg.technologia-tw.com"
|
||||
|
||||
when {
|
||||
rawPath.isBlank() -> ""
|
||||
// 如果後端已經給了完整網址(包含 http 或 https),就直接使用
|
||||
rawPath.startsWith("http") -> rawPath
|
||||
|
||||
else -> {
|
||||
// 💡 關鍵安全處理:先把開頭可能重複的 "/" 或 "uploads/" 乾淨地剝離,避免網址重複拼接
|
||||
val cleanPath = rawPath.removePrefix("/").removePrefix("uploads/")
|
||||
|
||||
if (cleanPath.startsWith("attachment/")) {
|
||||
"$baseUrl/uploads/$cleanPath"
|
||||
} else {
|
||||
|
|
@ -549,8 +536,6 @@ fun MessageRow(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val finalBubbleColor = remember(message.status, isHighlight) {
|
||||
val baseColor = when {
|
||||
isMe && isHighlight -> Color(0xFFB4E197)
|
||||
|
|
@ -558,13 +543,7 @@ fun MessageRow(
|
|||
!isMe && isHighlight -> Color(0xFFCFD8DC)
|
||||
else -> Color(0xFFECEFF1)
|
||||
}
|
||||
|
||||
// ⏳ 如果是發送中,直接讓顏色本身帶 alpha(例如 0.75f,防止被背景吃掉)
|
||||
if (message.status == MessageStatus.SENDING) {
|
||||
baseColor.copy(alpha = 0.75f)
|
||||
} else {
|
||||
baseColor // 成功或失敗時維持實色
|
||||
}
|
||||
if (message.status == MessageStatus.SENDING) baseColor.copy(alpha = 0.75f) else baseColor
|
||||
}
|
||||
|
||||
Row(
|
||||
|
|
@ -572,12 +551,10 @@ fun MessageRow(
|
|||
.fillMaxWidth()
|
||||
.then(if (isHighlight) Modifier.background(Color(0x33FF9800), RoundedCornerShape(12.dp)) else Modifier)
|
||||
) {
|
||||
//message container
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||
,
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
horizontalArrangement = if (isMe) Arrangement.End else Arrangement.Start
|
||||
) {
|
||||
if (!isMe) {
|
||||
|
|
@ -587,114 +564,92 @@ fun MessageRow(
|
|||
)
|
||||
}
|
||||
|
||||
// 💡 核心優化:如果是「我發的」且「發送失敗」,我們要把驚嘆號跟氣泡橫向並排
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
|
||||
// 🛑 如果是我發的訊息,且發送失敗(FAILED),就把紅色驚嘆號顯示在氣泡「左邊」
|
||||
if (isMe && message.status == MessageStatus.FAILED) {
|
||||
Icon(
|
||||
imageVector = androidx.compose.material.icons.Icons.Default.Warning, // 需確保有 import
|
||||
imageVector = androidx.compose.material.icons.Icons.Default.Warning,
|
||||
contentDescription = "發送失敗",
|
||||
tint = Color.Red,
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clickable {
|
||||
// 💡 加分功能:未來可以在這裡綁定「點擊重發」的 viewModel.sendMessage(...) 邏輯
|
||||
}
|
||||
modifier = Modifier.size(20.dp).clickable { /* 重發邏輯 */ }
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = if (isMe) Alignment.End else Alignment.Start
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 280.dp)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 12.dp, topEnd = 12.dp,
|
||||
bottomStart = if (isMe) 12.dp else 0.dp,
|
||||
bottomEnd = if (isMe) 0.dp else 12.dp
|
||||
)
|
||||
)
|
||||
.combinedClickable(
|
||||
onLongClick = { onReplyClick(message) },
|
||||
onClick = { onRowClick(message) }
|
||||
)
|
||||
.background(color = finalBubbleColor)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column {
|
||||
|
||||
// 🌟 核心修復:如果存在被回覆的訊息,在這裡將它渲染出來!
|
||||
// 🌟 核心分流:如果是圖片,不穿氣泡外衣,直接渲染
|
||||
if (isImage) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 240.dp) // 限制圖片最大寬度
|
||||
.combinedClickable(
|
||||
onLongClick = { onReplyClick(message) },
|
||||
onClick = { onRowClick(message) }
|
||||
),
|
||||
horizontalAlignment = if (isMe) Alignment.End else Alignment.Start
|
||||
) {
|
||||
// 1. 如果有被回覆的訊息,依然顯示在圖片上方(可選,如果不需要也可以移進去文字氣泡)
|
||||
message.repliedMessage?.let { replied ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 6.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(Color(0x1A000000)) // 給予一層淡淡的黑色半透明背景做為區隔
|
||||
.padding(start = 6.dp) // 留空間給左側邊條
|
||||
) {
|
||||
// 左側的垂直裝飾邊條
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(3.dp)
|
||||
.height(32.dp)
|
||||
.background(if (isMe) Color(0xFF4CAF50) else Color(0xFF78909C)) // 根據是誰發的決定邊條顏色
|
||||
)
|
||||
|
||||
// 被回覆訊息的內容預覽
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (replied.senderId == currentUserId) "你" else partnerDisplayName,
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
text = if (replied.attachment?.mimeType?.startsWith("image/") == true) "[圖片]" else replied.content,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1, // 限制一行,避免回覆訊息太長塞爆畫面
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
RepliedMessagePreview(replied, isMe, partnerDisplayName, currentUserId)
|
||||
}
|
||||
|
||||
// 訊息主體內容(文字或圖片)
|
||||
if (isImage) {
|
||||
// 💡 移除外層固定限制,改由 AsyncImage 自己決定尺寸
|
||||
AsyncImage(
|
||||
model = finalImageModel,
|
||||
contentDescription = "聊天圖片",
|
||||
// 🌟 關鍵 1:使用 ContentScale.Fit 或 Inside
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
// 🌟 關鍵 2:限制最大寬高,避免超大圖塞滿螢幕
|
||||
.sizeIn(maxWidth = 240.dp, maxHeight = 300.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.LightGray),
|
||||
onError = { errorState ->
|
||||
android.util.Log.e("ChatImageError", "原因: ${errorState.result.throwable}")
|
||||
}
|
||||
// 2. 獨立的圖片元件(完全沒有外層氣泡背景色與 Padding)
|
||||
AsyncImage(
|
||||
model = finalImageModel,
|
||||
contentDescription = "聊天圖片",
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.sizeIn(maxWidth = 240.dp, maxHeight = 300.dp)
|
||||
.clip(RoundedCornerShape(12.dp)) // 圖片自帶圓角,更美觀
|
||||
.border(
|
||||
width = 0.5.dp,
|
||||
color = Color.LightGray.copy(alpha = 0.4f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.background(Color(0xFFF5F5F5)),
|
||||
onError = { errorState ->
|
||||
android.util.Log.e("ChatImageError", "原因: ${errorState.result.throwable}")
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 🌟 文字訊息:維持原本的 Box 氣泡樣式
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 280.dp)
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = 12.dp, topEnd = 12.dp,
|
||||
bottomStart = if (isMe) 12.dp else 0.dp,
|
||||
bottomEnd = if (isMe) 0.dp else 12.dp
|
||||
)
|
||||
)
|
||||
.combinedClickable(
|
||||
onLongClick = { onReplyClick(message) },
|
||||
onClick = { onRowClick(message) }
|
||||
)
|
||||
.background(color = finalBubbleColor)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column {
|
||||
message.repliedMessage?.let { replied ->
|
||||
RepliedMessagePreview(replied, isMe, partnerDisplayName, currentUserId)
|
||||
}
|
||||
|
||||
} else {
|
||||
Text(
|
||||
text = rawContent,
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontSize = 16.sp
|
||||
)
|
||||
style = MaterialTheme.typography.bodyMedium.copy(fontSize = 16.sp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 按讚/反應功能(無論文字或圖片都靠下對齊顯示)
|
||||
if (!message.reactions.isNullOrEmpty()) {
|
||||
ReactionRow(
|
||||
reactions = message.reactions,
|
||||
|
|
@ -702,10 +657,40 @@ fun MessageRow(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 🛑 如果是對方發的訊息(雖然對方理論上不會有 FAILED 狀態,安全起見做對稱),或是想放右邊的提示
|
||||
// 可以依此類推。通常聊天軟體只會處理自己發送失敗的提示。
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 💡 提取出的「被回覆訊息預覽」組件,避免重複代碼並維持排版乾淨
|
||||
@Composable
|
||||
private fun RepliedMessagePreview(
|
||||
replied: Message,
|
||||
isMe: Boolean,
|
||||
partnerDisplayName: String,
|
||||
currentUserId: String
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 6.dp)
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(Color(0x1A000000))
|
||||
.padding(start = 6.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(3.dp)
|
||||
.height(22.dp)
|
||||
.background(if (isMe) Color(0xFF4CAF50) else Color(0xFF78909C))
|
||||
)
|
||||
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
|
||||
Text(
|
||||
text = if (replied.attachment?.mimeType?.startsWith("image/") == true) "[圖片]" else replied.content ?: "",
|
||||
style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue