auth to server-V52
This commit is contained in:
parent
356ee52cff
commit
0e8ecb74b6
4 changed files with 73 additions and 38 deletions
|
|
@ -39,6 +39,12 @@ interface TecnologiaApi {
|
||||||
@Part file: MultipartBody.Part
|
@Part file: MultipartBody.Part
|
||||||
): Response<Unit>
|
): Response<Unit>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("api/me/banner")
|
||||||
|
suspend fun uploadBanner(
|
||||||
|
@Part file: MultipartBody.Part
|
||||||
|
): Response<Unit>
|
||||||
|
|
||||||
// 發送好友請求
|
// 發送好友請求
|
||||||
@POST("api/friends/request")
|
@POST("api/friends/request")
|
||||||
suspend fun sendFriendRequest(
|
suspend fun sendFriendRequest(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.example.login_v3.data.api.TecnologiaApi
|
||||||
import com.example.login_v3.data.api.api_class.UserProfile
|
import com.example.login_v3.data.api.api_class.UserProfile
|
||||||
import com.example.login_v3.data.api.api_class.UserProfileUpdateRequest
|
import com.example.login_v3.data.api.api_class.UserProfileUpdateRequest
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
|
import retrofit2.Response
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PersonalProfileRepository @Inject constructor(
|
class PersonalProfileRepository @Inject constructor(
|
||||||
|
|
@ -40,10 +41,13 @@ class PersonalProfileRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 上傳頭像圖片 (POST)
|
// 3. 上傳頭像圖片 (POST)
|
||||||
suspend fun uploadAvatar(avatarPart: MultipartBody.Part): Result<Unit> {
|
// 通用的上傳邏輯
|
||||||
|
private suspend fun uploadImage(
|
||||||
|
filePart: MultipartBody.Part,
|
||||||
|
uploadAction: suspend (MultipartBody.Part) -> Response<Unit>
|
||||||
|
): Result<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val response = api.uploadAvatar(avatarPart)
|
val response = uploadAction(filePart)
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -53,4 +57,11 @@ class PersonalProfileRepository @Inject constructor(
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 外部呼叫時依然保有語意化的名稱
|
||||||
|
suspend fun uploadAvatar(avatarPart: MultipartBody.Part) =
|
||||||
|
uploadImage(avatarPart, api::uploadAvatar)
|
||||||
|
|
||||||
|
suspend fun uploadBanner(bannerPart: MultipartBody.Part) =
|
||||||
|
uploadImage(bannerPart, api::uploadBanner)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.example.login_v3.home.setting.setting_detail_page.detail_UI
|
package com.example.login_v3.home.setting.setting_detail_page.detail_UI
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.PickVisualMediaRequest
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
|
|
@ -107,14 +108,17 @@ fun Loaded_setting_profile_page(
|
||||||
|
|
||||||
//image chooser
|
//image chooser
|
||||||
val context = LocalContext.current // 務必獲取 Context,uploadAvatar 需要它來解析 Uri
|
val context = LocalContext.current // 務必獲取 Context,uploadAvatar 需要它來解析 Uri
|
||||||
val launcher = rememberLauncherForActivityResult(
|
//avatar
|
||||||
contract = ActivityResultContracts.PickVisualMedia()
|
val avatarLauncher = rememberLauncherForActivityResult(
|
||||||
) { uri ->
|
contract = ActivityResultContracts.PickVisualMedia() // 這裡改掉
|
||||||
if (uri != null) {
|
) { uri: Uri? ->
|
||||||
// ✅ 修正:呼叫上傳圖片的函式,而不是更新網址字串的函式
|
uri?.let { viewModel.uploadAvatar(context, it) }
|
||||||
viewModel.uploadAvatar(context, uri)
|
}
|
||||||
Log.d("AvatarURL", "目前的圖片網址為: ${profile.avatar_url}")
|
//banner
|
||||||
}
|
val bannerLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.PickVisualMedia() // 這裡改掉
|
||||||
|
) { uri: Uri? ->
|
||||||
|
uri?.let { viewModel.uploadBanner(context, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -160,13 +164,15 @@ fun Loaded_setting_profile_page(
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
){
|
){
|
||||||
// 1. 封面圖 (背景)
|
// 1. 封面圖 (背景)
|
||||||
Image(
|
AsyncImage(
|
||||||
painter = painterResource(id = R.drawable.thumbnail_v1),
|
model = profile.banner_url,
|
||||||
contentDescription = null,
|
contentDescription = "Avatar of ${profile.display_name}",
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(180.dp)
|
.height(180.dp)
|
||||||
|
,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
error = painterResource(R.drawable.thumbnail_v1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,7 +195,7 @@ fun Loaded_setting_profile_page(
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.clickable {
|
.clickable {
|
||||||
// 假設你拿到了一個新網址
|
// 假設你拿到了一個新網址
|
||||||
launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
avatarLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||||
}
|
}
|
||||||
,
|
,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
|
|
|
||||||
|
|
@ -81,38 +81,50 @@ class PersonalProfileViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// upload image
|
// upload image
|
||||||
fun uploadAvatar(context: Context, uri: Uri) {
|
// 1. 定義一個通用的處理邏輯
|
||||||
|
private fun handleImageUpload(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri,
|
||||||
|
isAvatar: Boolean // 用來區分檔案名稱或特定邏輯
|
||||||
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// 如果你想要在上傳時讓 UI 顯示 Loading,可以在這開啟
|
_uiState.value = ProfileUiState.Loading
|
||||||
// _uiState.value = ProfileUiState.Loading
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 從 Uri 取得 InputStream 並讀取 Bytes
|
// 從 Uri 讀取檔案
|
||||||
val inputStream = context.contentResolver.openInputStream(uri)
|
val bytes = context.contentResolver.openInputStream(uri)?.use { it.readBytes() }
|
||||||
val bytes = inputStream?.use { it.readBytes() } ?: return@launch
|
if (bytes == null) {
|
||||||
|
_uiState.value = ProfileUiState.Error("無法讀取檔案")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 封裝成 RequestBody (指定為圖片格式)
|
|
||||||
val requestFile = bytes.toRequestBody("image/*".toMediaTypeOrNull())
|
val requestFile = bytes.toRequestBody("image/*".toMediaTypeOrNull())
|
||||||
|
|
||||||
// 3. 建立 MultipartBody.Part
|
// 根據類型決定檔名(雖然通常後端只看 Part Name,但給正確副檔名比較保險)
|
||||||
// 注意:"avatar" 必須跟後端 @Part("avatar") 裡面的名稱一模一樣
|
val fileName = if (isAvatar) "avatar.jpg" else "banner.jpg"
|
||||||
val body = MultipartBody.Part.createFormData("file", "avatar.jpg", requestFile)
|
val body = MultipartBody.Part.createFormData("file", fileName, requestFile)
|
||||||
|
|
||||||
|
// 根據類型呼叫對應的 Repository 方法
|
||||||
|
val result = if (isAvatar) {
|
||||||
|
repository.uploadAvatar(body)
|
||||||
|
} else {
|
||||||
|
repository.uploadBanner(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.onSuccess {
|
||||||
|
fetchProfile() // 成功後統一刷新資料
|
||||||
|
}.onFailure { e ->
|
||||||
|
_uiState.value = ProfileUiState.Error(e.localizedMessage ?: "上傳失敗")
|
||||||
|
}
|
||||||
|
|
||||||
// 4. 呼叫 Repository
|
|
||||||
repository.uploadAvatar(body)
|
|
||||||
.onSuccess {
|
|
||||||
// 上傳成功後,最保險的做法是重新 fetch 一次個人資料
|
|
||||||
// 這樣 UI 上的頭像網址才會更新成後端最新儲存的網址
|
|
||||||
fetchProfile()
|
|
||||||
}
|
|
||||||
.onFailure { e ->
|
|
||||||
// 處理錯誤,例如發送一個錯誤訊息給 UI
|
|
||||||
_uiState.value = ProfileUiState.Error(e.localizedMessage ?: "上傳失敗")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_uiState.value = ProfileUiState.Error("讀取檔案出錯: ${e.message}")
|
_uiState.value = ProfileUiState.Error("處理檔案時發生錯誤: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 對外暴露的簡潔方法
|
||||||
|
fun uploadAvatar(context: Context, uri: Uri) = handleImageUpload(context, uri, true)
|
||||||
|
fun uploadBanner(context: Context, uri: Uri) = handleImageUpload(context, uri, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue