dm-37
This commit is contained in:
parent
773af7b5cd
commit
542cd46881
4 changed files with 112 additions and 17 deletions
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -395,3 +380,19 @@ 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
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue