This commit is contained in:
Eric85710 2026-05-26 14:44:03 +08:00
parent 2d1b15da7b
commit 57e546f733
2 changed files with 49 additions and 14 deletions

View file

@ -1,6 +1,8 @@
package com.example.login_v3.data.repository.dm package com.example.login_v3.data.repository.dm
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns import android.provider.OpenableColumns
import com.example.login_v3.data.api.TecnologiaApi import com.example.login_v3.data.api.TecnologiaApi
@ -13,6 +15,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.io.ByteArrayOutputStream
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -130,10 +133,10 @@ class ChatRoomsRepository @Inject constructor(
//upload file //upload file
suspend fun uploadAttachment(roomId: String, fileUri: Uri): Result<Message> { suspend fun uploadAttachment(roomId: String, fileUri: Uri): Result<Message> {
return try { return try {
// 1. 透過 ContentResolver 取得檔案的遮罩名稱與大小
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
var fileName = "upload_file" var fileName = "upload_file"
// 1. 透過 ContentResolver 取得檔案的名稱
contentResolver.query(fileUri, null, null, null, null)?.use { cursor -> contentResolver.query(fileUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex != -1 && cursor.moveToFirst()) { if (nameIndex != -1 && cursor.moveToFirst()) {
@ -141,15 +144,51 @@ class ChatRoomsRepository @Inject constructor(
} }
} }
// 2. 取得檔案的 MIME 類型 (例如 image/jpeg, image/png) // 2. 取得原始 MIME 類型
val mimeType = contentResolver.getType(fileUri) ?: "application/octet-stream" val rawMimeType = contentResolver.getType(fileUri) ?: "application/octet-stream"
// 3. 讀取檔案 Byte 陣列並轉換為 RequestBody val fileBytes: ByteArray
val finalMimeType: String
// ✨【關鍵轉檔邏輯】:如果是 HEIC/HEIF,在前端直接壓成 JPG
if (rawMimeType == "image/heic" || rawMimeType == "image/heif") {
val inputStream = contentResolver.openInputStream(fileUri) val inputStream = contentResolver.openInputStream(fileUri)
?: return Result.failure(Exception("無法開啟檔案輸入流 (InputStream is null)")) ?: return Result.failure(Exception("無法開啟檔案輸入流"))
val fileBytes = inputStream.use { it.readBytes() } // 利用 BitmapFactory 解碼,Android 系統會自動幫你把 HEIC 解成標準 Bitmap
val requestBody = fileBytes.toRequestBody(mimeType.toMediaTypeOrNull()) val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream.close()
if (bitmap != null) {
val outputStream = ByteArrayOutputStream()
// 壓縮成 JPEG 格式,品質設為 85-90(兼顧畫質與檔案大小)
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
fileBytes = outputStream.toByteArray()
outputStream.close()
bitmap.recycle() // 釋放記憶體
finalMimeType = "image/jpeg"
// 修改副檔名為 .jpg,讓後端正確識別儲存
val dotIndex = fileName.lastIndexOf(".")
fileName = if (dotIndex != -1) {
"${fileName.substring(0, dotIndex)}.jpg"
} else {
"$fileName.jpg"
}
} else {
return Result.failure(Exception("HEIC 圖片解碼失敗"))
}
} else {
// 非 HEIC 檔案(普通的 JPG, PNG 等),走原本的邏輯,直接讀取原始 Byte
val inputStream = contentResolver.openInputStream(fileUri)
?: return Result.failure(Exception("無法開啟檔案輸入流"))
fileBytes = inputStream.use { it.readBytes() }
finalMimeType = rawMimeType
}
// 3. 轉換為 RequestBody
val requestBody = fileBytes.toRequestBody(finalMimeType.toMediaTypeOrNull())
// 4. 打包成 MultipartBody.Part (後端接收的 Key 值設定為 "file") // 4. 打包成 MultipartBody.Part (後端接收的 Key 值設定為 "file")
val multipartBody = MultipartBody.Part.createFormData("file", fileName, requestBody) val multipartBody = MultipartBody.Part.createFormData("file", fileName, requestBody)
@ -171,6 +210,5 @@ class ChatRoomsRepository @Inject constructor(
} catch (e: Exception) { } catch (e: Exception) {
Result.failure(e) Result.failure(e)
} }
} }
} }

View file

@ -97,18 +97,15 @@ fun MessageMessaging(
} }
// ✨ 監聽發送狀態,如果失敗了就彈出提示並重置狀態 // ✨ 監聽發送狀態,如果失敗了就彈出提示並重置狀態
// 修改後的 LaunchedEffect
LaunchedEffect(sendStatus) { LaunchedEffect(sendStatus) {
if (sendStatus is SendMessageState.Error) { if (sendStatus is SendMessageState.Error) {
val errorMsg = (sendStatus as SendMessageState.Error).message val errorMsg = (sendStatus as SendMessageState.Error).message
Toast.makeText(context, "發送失敗: $errorMsg", Toast.LENGTH_SHORT).show() Toast.makeText(context, "發送失敗: $errorMsg", Toast.LENGTH_SHORT).show()
viewModel.resetSendMessageState() viewModel.resetSendMessageState()
} else if (sendStatus is SendMessageState.Success) { } else if (sendStatus is SendMessageState.Success) {
// 發送成功後,重置狀態以便下一次發送 // ✨ 發送成功後,只重置狀態。讓 ViewModel 的 uiState 自動推播新訊息給 UI 即可!
viewModel.resetSendMessageState() viewModel.resetSendMessageState()
// 【優化體驗】:你可以在這裡重新呼叫 viewModel.loadMessages(roomId) 刷新列表
// 或者如果你的 ViewModel 已經會自動把新訊息塞進 uiState,這裡就什麼都不用做。
viewModel.loadMessages(roomId)
} }
} }