diff --git a/app/src/main/java/com/example/login_v3/data/api/Main_API_Service.kt b/app/src/main/java/com/example/login_v3/data/api/Main_API_Service.kt index 1a75667..5c5fc10 100644 --- a/app/src/main/java/com/example/login_v3/data/api/Main_API_Service.kt +++ b/app/src/main/java/com/example/login_v3/data/api/Main_API_Service.kt @@ -103,4 +103,11 @@ interface TecnologiaApi { @Body request: SendMessageRequest ): Response + //send image + @Multipart + @POST("api/dm/{room_id}/attachment") + suspend fun uploadAttachment( + @Path("room_id") roomId: String, + @Part file: MultipartBody.Part + ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/data/repository/dm/DM_Basic_Repository.kt b/app/src/main/java/com/example/login_v3/data/repository/dm/DM_Basic_Repository.kt index 8c1e772..3d4a442 100644 --- a/app/src/main/java/com/example/login_v3/data/repository/dm/DM_Basic_Repository.kt +++ b/app/src/main/java/com/example/login_v3/data/repository/dm/DM_Basic_Repository.kt @@ -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 { @@ -117,4 +125,52 @@ class ChatRoomsRepository @Inject constructor( Result.failure(e) } } + + + //upload file + suspend fun uploadAttachment(roomId: String, fileUri: Uri): Result { + 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) + } + + } } \ No newline at end of file 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 93de9a5..19da107 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 @@ -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 + ) } \ No newline at end of file diff --git a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt index 6a67738..af1db4c 100644 --- a/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt +++ b/app/src/main/java/com/example/login_v3/home/Message/ViewModel/Detail/Message_Messaging_ViewModel.kt @@ -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 }