diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt index 038e40a..5f8ff5e 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging.kt @@ -1,6 +1,7 @@ package com.example.login_v3.home.Message.UI.Detail import android.net.Uri +import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -307,6 +308,7 @@ fun MessageList( } } +private fun String?.isNullTabOrBlank(): Boolean = this == null || this.trim().isBlank() @Composable fun MessageRow( message: Message, @@ -324,8 +326,30 @@ fun MessageRow( val isLegacyImage = contentLowerCase.startsWith("http") && imageExtensions.any { contentLowerCase.contains(it) } val isImage = isAttachmentImage || isLegacyImage + val rawContent = message.content ?: "" - val finalImageModel = if (rawContent.startsWith("/")) "http://192.168.0.217$rawContent" else rawContent + // 💡 核心修正:利用 remember 根據 message 狀態動態計算正確的圖片 URL + val finalImageModel = remember(message) { + // 1. 如果有 attachment,優先去拿裡面的 filename;如果沒有,再拿一般的 content + // 2. 假設後端儲存路徑通常是在某個特定資料夾(例如 /uploads/),如果 filename 沒帶路徑,記得補上 + val rawPath = if (isAttachmentImage && !message.attachment?.filename.isNullTabOrBlank()) { + message.attachment!!.filename + } else { + message.content + } + + when { + rawPath.isBlank() -> "" + rawPath.startsWith("https") -> rawPath // 已經是完整網址,直接回傳 + + // 💡 注意:請根據你後端圖片實際存放的路由修改這裡。 + // 如果後端 filename 只有 "abc.jpg",而實際上網址是 "http://192.168.0.217/uploads/abc.jpg" + // 你需要把下面改成 "/uploads/$rawPath" + rawPath.startsWith("/") -> "https://192.168.0.217$rawPath" + + else -> "https://192.168.0.217/$rawPath" + } + } //emoji value var showEmojiPanel by remember { mutableStateOf(false) } @@ -424,7 +448,15 @@ fun MessageRow( contentScale = ContentScale.Fit, modifier = Modifier .fillMaxWidth() - .heightIn(max = 300.dp) + .heightIn(max = 300.dp), + // 🛠️ 移除了 unresolved 的 debugPlaceholder + onError = { errorState -> + // 這樣就能在 Logcat 裡過濾 "ChatImageError" 關鍵字,看看到底是哪個網址拼錯了! + android.util.Log.e( + "ChatImageError", + "圖片載入失敗, 網址為: $finalImageModel, 原因: ${errorState.result.throwable}" + ) + } ) } } else { diff --git a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging_detail/MessageInputBar.kt b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging_detail/MessageInputBar.kt index ef71dff..f383194 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging_detail/MessageInputBar.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/UI/Detail/Message_Messaging_detail/MessageInputBar.kt @@ -5,6 +5,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -32,6 +33,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -61,100 +63,112 @@ fun MessageInputBar( .navigationBarsPadding() // 自動適應 Android 系統導覽列高度 .padding(horizontal = 16.dp, vertical = 8.dp) // 懸浮的外邊距 ) { - // 使用 Surface 或 Card 來實作圓角與陰影懸浮感 - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(16.dp), // 整體大圓角化 - color = Color.White, - tonalElevation = 6.dp, // 頂級懸浮感 - shadowElevation = 6.dp // 實體陰影 - ) { - Column(modifier = Modifier.fillMaxWidth()) { - // 1. 回覆預覽列 (加入頂部圓角修飾) - AnimatedVisibility(visible = replyingMessage != null) { - if (replyingMessage != null) { - Row( - modifier = Modifier - .fillMaxWidth() - .background(Color(0xFFF8F9FA)) // 更輕盈的底色 - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Column(modifier = Modifier.weight(1f)) { - Text( - text = replyingMessage.content, - style = MaterialTheme.typography.bodySmall, - color = Color.Gray, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - IconButton(onClick = onCancelReply, modifier = Modifier.size(24.dp)) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "取消回覆", - tint = Color.Gray - ) - } + //glass effect + Box( + modifier = Modifier + .matchParentSize() + .blur(10.dp) + ) + Box( + modifier = Modifier + .matchParentSize() + .background( + color = MaterialTheme.colorScheme.primary, + RoundedCornerShape(16.dp) + ) + .border( + 1.dp, + Color.White.copy(alpha = 0.3f), + RoundedCornerShape(16.dp) + ) + ) + + Column(modifier = Modifier.fillMaxWidth()) { + + // 1. 回覆預覽列 (加入頂部圓角修飾) + AnimatedVisibility(visible = replyingMessage != null) { + if (replyingMessage != null) { + Row( + modifier = Modifier + .fillMaxWidth() + .background(Color(0xFFF8F9FA)) // 更輕盈的底色 + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = replyingMessage.content, + style = MaterialTheme.typography.bodySmall, + color = Color.Gray, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + IconButton(onClick = onCancelReply, modifier = Modifier.size(24.dp)) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "取消回覆", + tint = Color.Gray + ) } } } + } - // 2. 實際的輸入工具列 - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - // 圖片選擇按鈕 - IconButton(onClick = { imagePickerLauncher.launch("image/*") }) { - Icon( - imageVector = Icons.Default.Photo, - contentDescription = "選擇圖片", - tint = Color.Gray - ) - } - - // 輸入文字框 (改為內嵌樣式,去掉自帶的邊框,讓整體更有一體感) - BasicTextField( - value = textState, - onValueChange = { textState = it }, - modifier = Modifier - .weight(1f) - .padding(horizontal = 8.dp), - textStyle = MaterialTheme.typography.bodyLarge, - decorationBox = { innerTextField -> - if (textState.isEmpty()) { - Text("請輸入訊息...", color = Color.LightGray) - } - innerTextField() - } + // 2. 實際的輸入工具列 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 圖片選擇按鈕 + IconButton(onClick = { imagePickerLauncher.launch("image/*") }) { + Icon( + imageVector = Icons.Default.Photo, + contentDescription = "選擇圖片", + tint = Color.Gray ) + } - // 送出按鈕 - IconButton( - onClick = { - if (textState.isNotBlank() && !isLoading) { - onSendClick(textState) - textState = "" - } - }, - enabled = textState.isNotBlank() && !isLoading - ) { - if (isLoading) { - CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp) - } else { - // 加上小背景讓送出按鈕更有重點 - val iconColor = if (textState.isNotBlank()) Color(0xFFDA7029) else Color.LightGray - Icon( - imageVector = Icons.Default.Send, - contentDescription = "送出", - tint = iconColor - ) + // 輸入文字框 (改為內嵌樣式,去掉自帶的邊框,讓整體更有一體感) + BasicTextField( + value = textState, + onValueChange = { textState = it }, + modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp), + textStyle = MaterialTheme.typography.bodyLarge, + decorationBox = { innerTextField -> + if (textState.isEmpty()) { + Text("請輸入訊息...", color = Color.LightGray) } + innerTextField() + } + ) + + // 送出按鈕 + IconButton( + onClick = { + if (textState.isNotBlank() && !isLoading) { + onSendClick(textState) + textState = "" + } + }, + enabled = textState.isNotBlank() && !isLoading + ) { + if (isLoading) { + CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp) + } else { + // 加上小背景讓送出按鈕更有重點 + val iconColor = if (textState.isNotBlank()) Color(0xFFDA7029) else Color.LightGray + Icon( + imageVector = Icons.Default.Send, + contentDescription = "送出", + tint = iconColor + ) } } } diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index ad77b6c..9a07370 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,6 +1,6 @@ - + 192.168.0.217