This commit is contained in:
Eric85710 2026-05-25 14:11:34 +08:00
parent 773af7b5cd
commit 542cd46881
4 changed files with 112 additions and 17 deletions

View file

@ -103,4 +103,11 @@ interface TecnologiaApi {
@Body request: SendMessageRequest
): Response<Message>
//send image
@Multipart
@POST("api/dm/{room_id}/attachment")
suspend fun uploadAttachment(
@Path("room_id") roomId: String,
@Part file: MultipartBody.Part
): Response<Message>
}

View file

@ -1,17 +1,25 @@
package com.example.login_v3.data.repository.dm
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import com.example.login_v3.data.api.TecnologiaApi
import com.example.login_v3.data.api.api_class.ChatRoom
import com.example.login_v3.data.api.api_class.Message
import com.example.login_v3.data.api.api_class.MessageResponse
import com.example.login_v3.data.api.api_class.RoomListResponse
import com.example.login_v3.data.api.api_class.SendMessageRequest
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ChatRoomsRepository @Inject constructor(
private val api: TecnologiaApi
private val api: TecnologiaApi,
@ApplicationContext private val context: Context // 注入 ApplicationContext 用於處理 Uri 檔案
) {
suspend fun fetchRooms(): Result<RoomListResponse> {
@ -117,4 +125,52 @@ class ChatRoomsRepository @Inject constructor(
Result.failure(e)
}
}
//upload file
suspend fun uploadAttachment(roomId: String, fileUri: Uri): Result<Message> {
return try {
// 1. 透過 ContentResolver 取得檔案的遮罩名稱與大小
val contentResolver = context.contentResolver
var fileName = "upload_file"
contentResolver.query(fileUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex != -1 && cursor.moveToFirst()) {
fileName = cursor.getString(nameIndex)
}
}
// 2. 取得檔案的 MIME 類型 (例如 image/jpeg, image/png)
val mimeType = contentResolver.getType(fileUri) ?: "application/octet-stream"
// 3. 讀取檔案 Byte 陣列並轉換為 RequestBody
val inputStream = contentResolver.openInputStream(fileUri)
?: return Result.failure(Exception("無法開啟檔案輸入流 (InputStream is null)"))
val fileBytes = inputStream.use { it.readBytes() }
val requestBody = fileBytes.toRequestBody(mimeType.toMediaTypeOrNull())
// 4. 打包成 MultipartBody.Part (後端接收的 Key 值設定為 "file")
val multipartBody = MultipartBody.Part.createFormData("file", fileName, requestBody)
// 5. 呼叫 API
val response = api.uploadAttachment(roomId, multipartBody)
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
Result.success(body)
} else {
Result.failure(Exception("上傳成功但回應身體為空 (Empty response body)"))
}
} else {
val errorMsg = response.errorBody()?.string() ?: "未知錯誤"
Result.failure(Exception("上傳檔案失敗,錯誤碼: ${response.code()}, 訊息: $errorMsg"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}

View file

@ -321,21 +321,6 @@ fun MessageRow(
}
}
@Composable
fun UserAvatar(
avatarUrl: Any?, // ✨ 改成 Any?
modifier: Modifier = Modifier
) {
AsyncImage(
model = avatarUrl ?: R.drawable.avatar_v1, // 如果為 null,就用預設圖
contentDescription = "用戶頭像",
modifier = modifier
.background(Color.LightGray, shape = CircleShape)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
}
@Composable
fun MessageInputBar(
@ -394,4 +379,20 @@ fun MessageInputBar(
}
}
}
}
@Composable
fun UserAvatar(
avatarUrl: Any?, // ✨ 改成 Any?
modifier: Modifier = Modifier
) {
AsyncImage(
model = avatarUrl ?: R.drawable.avatar_v1, // 如果為 null,就用預設圖
contentDescription = "用戶頭像",
modifier = modifier
.background(Color.LightGray, shape = CircleShape)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
}

View file

@ -1,6 +1,7 @@
package com.example.login_v3.home.Message.ViewModel.Detail
import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -108,6 +109,7 @@ class ChatViewModel @Inject constructor(
}
//upload image
fun sendMessage(roomId: String, content: String, replyToId: String? = null) {
if (content.isBlank()) return
@ -136,7 +138,36 @@ class ChatViewModel @Inject constructor(
}
}
// ✨ 4. 新增:重設發送狀態的 function
//upload file
fun uploadAttachment(roomId: String, fileUri: Uri) {
viewModelScope.launch {
// 1. 將發送狀態切換為 Loading,讓 UI 顯示進度條
_sendMessageState.value = SendMessageState.Loading
// 2. 呼叫 Repository 處理檔案並上傳
val result = repository.uploadAttachment(roomId, fileUri)
result.onSuccess { newMessage ->
// 3. 上傳成功,更新發送狀態
_sendMessageState.value = SendMessageState.Success(newMessage)
// 😎【體驗優化】:跟發送文字訊息一樣,直接把帶有圖片的新訊息塞進現有的 UI 列表裡
val currentState = _uiState.value
if (currentState is MessagesUiState.Success) {
val updatedMessages = currentState.messages + newMessage
_uiState.value = currentState.copy(messages = updatedMessages)
}
}.onFailure { error ->
Log.e("ChatViewModel", "Upload attachment failed", error)
// 4. 上傳失敗,通知 UI 顯示錯誤
_sendMessageState.value = SendMessageState.Error(error.message ?: "上傳失敗,請稍後再試")
}
}
}
//新增:重設發送狀態的 function
fun resetSendMessageState() {
_sendMessageState.value = SendMessageState.Idle
}