This commit is contained in:
Eric85710 2026-05-25 14:28:47 +08:00
parent 542cd46881
commit 6898abd803
2 changed files with 47 additions and 22 deletions

View file

@ -105,7 +105,7 @@ interface TecnologiaApi {
//send image //send image
@Multipart @Multipart
@POST("api/dm/{room_id}/attachment") @POST("api/dm/{room_id}/attachments")
suspend fun uploadAttachment( suspend fun uploadAttachment(
@Path("room_id") roomId: String, @Path("room_id") roomId: String,
@Part file: MultipartBody.Part @Part file: MultipartBody.Part

View file

@ -1,7 +1,10 @@
package com.example.login_v3.home.Message.UI.Detail package com.example.login_v3.home.Message.UI.Detail
import android.net.Uri
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -26,6 +29,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Send import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -167,8 +171,11 @@ fun MessageMessaging(
MessageInputBar( MessageInputBar(
isLoading = sendStatus is SendMessageState.Loading, isLoading = sendStatus is SendMessageState.Loading,
onSendClick = { text -> onSendClick = { text ->
// 呼叫 ViewModel 的發送功能
viewModel.sendMessage(roomId = roomId, content = text) viewModel.sendMessage(roomId = roomId, content = text)
},
onImageSelected = { uri ->
// ✨ 這裡呼叫你先前在 ViewModel 寫好的上傳圖片功能
viewModel.uploadAttachment(roomId = roomId, fileUri = uri)
} }
) )
} }
@ -243,24 +250,23 @@ fun MessageRow(
partnerDisplayName: String, partnerDisplayName: String,
currentUserId: String currentUserId: String
) { ) {
// 1. ✨ 升級防禦力的判斷邏輯 // 1. ✨ 使用新的 attachment 欄位精準判斷是否為圖片,或是舊格式的網址判斷(相容舊資料)
val contentLowerCase = message.content.lowercase() val isAttachmentImage = message.attachment?.mimeType?.startsWith("image/") == true
val isImageUrl = contentLowerCase.startsWith("http") || contentLowerCase.contains("/uploads/")
// 改用 contains 包含圖片副檔名,防止網址後面有自帶參數(例如 ?width=300 之類的) // 保留你原本的舊資料防禦邏輯,避免舊的訊息爆掉
val isImageFormat = contentLowerCase.contains(".jpg") || val contentLowerCase = (message.content ?: "").lowercase()
contentLowerCase.contains(".jpeg") || val isLegacyImage = contentLowerCase.startsWith("http") &&
contentLowerCase.contains(".png") || (contentLowerCase.contains(".jpg") || contentLowerCase.contains(".png") || contentLowerCase.contains(".webp"))
contentLowerCase.contains(".webp") ||
contentLowerCase.contains(".heic")
val isImage = isImageUrl && isImageFormat val isImage = isAttachmentImage || isLegacyImage
// 2. ✨ 補全圖片網址:如果網址是 /uploads/ 開頭,自動幫它加上後端的 Domain // 2. 補全圖片網址
val finalImageModel = if (message.content.startsWith("/")) { // 2. 補全圖片網址
"http://192.168.0.217${message.content}" // ⚠️ 請把這裡替換成你實際的後端伺服器網域 val rawContent = message.content ?: ""
val finalImageModel = if (rawContent.startsWith("/")) {
"http://192.168.0.217$rawContent"
} else { } else {
message.content rawContent
} }
Row( Row(
@ -291,7 +297,6 @@ fun MessageRow(
.padding(horizontal = 12.dp, vertical = 8.dp) .padding(horizontal = 12.dp, vertical = 8.dp)
) { ) {
Column { Column {
if (isImage) { if (isImage) {
Box( Box(
modifier = Modifier modifier = Modifier
@ -301,7 +306,7 @@ fun MessageRow(
.background(Color.LightGray) .background(Color.LightGray)
) { ) {
AsyncImage( AsyncImage(
model = finalImageModel, // ✨ 這裡要傳入補全後的完整網址 model = finalImageModel,
contentDescription = "聊天圖片", contentDescription = "聊天圖片",
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
modifier = Modifier modifier = Modifier
@ -312,7 +317,7 @@ fun MessageRow(
} }
} else { } else {
Text( Text(
text = message.content, text = rawContent,
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
} }
@ -326,12 +331,19 @@ fun MessageRow(
fun MessageInputBar( fun MessageInputBar(
isLoading: Boolean, isLoading: Boolean,
onSendClick: (String) -> Unit, onSendClick: (String) -> Unit,
onImageSelected: (Uri) -> Unit, // ✨ 新增:選取圖片後的回呼
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
var textInput by remember { mutableStateOf("") } var textInput by remember { mutableStateOf("") }
// 當發送成功、外面將 isLoading 從 true 變回 false 時,可以順便清空輸入框 // ✨ 建立相簿選取器的 Launcher
// 這裡直接在點擊發送時清空,或由外面控制皆可。 val imagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent()
) { uri: Uri? ->
if (uri != null) {
onImageSelected(uri) // 將選到的圖片 Uri 丟回給上層
}
}
Surface( Surface(
tonalElevation = 2.dp, tonalElevation = 2.dp,
@ -343,6 +355,19 @@ fun MessageInputBar(
.padding(horizontal = 8.dp, vertical = 4.dp), .padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// ✨ 新增:相簿按鈕
IconButton(
onClick = { imagePickerLauncher.launch("image/*") },
enabled = !isLoading
) {
Icon(
imageVector = Icons.Default.AddCircle, // 你也可以改用 Icons.Default.Image
contentDescription = "選擇圖片",
tint = Color(0xFFDA7029)
)
}
TextField( TextField(
value = textInput, value = textInput,
onValueChange = { textInput = it }, onValueChange = { textInput = it },
@ -362,7 +387,7 @@ fun MessageInputBar(
onClick = { onClick = {
if (textInput.isNotBlank() && !isLoading) { if (textInput.isNotBlank() && !isLoading) {
onSendClick(textInput) onSendClick(textInput)
textInput = "" // 點擊後立即清空輸入框 textInput = ""
} }
}, },
enabled = textInput.isNotBlank() && !isLoading enabled = textInput.isNotBlank() && !isLoading