auth to server-V77
This commit is contained in:
parent
a35d4412f3
commit
f7a3f64d61
5 changed files with 103 additions and 9 deletions
|
|
@ -11,6 +11,7 @@ import retrofit2.http.PATCH
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Part
|
import retrofit2.http.Part
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface TecnologiaApi {
|
interface TecnologiaApi {
|
||||||
|
|
||||||
|
|
@ -72,4 +73,10 @@ interface TecnologiaApi {
|
||||||
@Body request: AddFriendRequest
|
@Body request: AddFriendRequest
|
||||||
): Response<Unit>
|
): Response<Unit>
|
||||||
|
|
||||||
|
// 搜尋使用者
|
||||||
|
@GET("api/users/search")
|
||||||
|
suspend fun searchUsers(
|
||||||
|
@Query("q") query: String
|
||||||
|
): UserSearchResponse
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -79,3 +79,13 @@ val PendingFriendApiModel.fullPendingAvatarUrl: Any
|
||||||
} else {
|
} else {
|
||||||
"http://192.168.0.217$pendingAvatarUrl"
|
"http://192.168.0.217$pendingAvatarUrl"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根據你說的 API 回傳格式定義
|
||||||
|
data class UserSearchResponse(
|
||||||
|
val additionalProp1: Map<String, UserDetail> // 假設它是一個物件對應
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UserDetail(
|
||||||
|
val id: String,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package com.example.login_v3.data.repository.basic
|
package com.example.login_v3.data.repository.basic
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.example.login_v3.data.api.TecnologiaApi
|
import com.example.login_v3.data.api.TecnologiaApi
|
||||||
import com.example.login_v3.data.api.api_class.AddFriendRequest
|
import com.example.login_v3.data.api.api_class.AddFriendRequest
|
||||||
import com.example.login_v3.data.api.api_class.Friend
|
import com.example.login_v3.data.api.api_class.Friend
|
||||||
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
||||||
|
import com.example.login_v3.data.api.api_class.UserDetail
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -45,23 +47,51 @@ class FriendsRepository @Inject constructor(
|
||||||
apiService.rejectFriendRequest(id)
|
apiService.rejectFriendRequest(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 統一處理 Response<Unit> 的工具函式
|
//add friends
|
||||||
|
suspend fun sendFriendRequest(friendId: String): Result<Unit> {
|
||||||
|
Log.d("API_DEBUG", "準備發送好友申請,目標 ID: $friendId")
|
||||||
|
val result = safeApiCall {
|
||||||
|
val request = AddFriendRequest(friendId = friendId)
|
||||||
|
apiService.sendFriendRequest(request)
|
||||||
|
}
|
||||||
|
result.onFailure { Log.e("API_DEBUG", "sendFriendRequest 最終失敗: ${it.message}") }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 確保「全類別」只有這一個 safeApiCall 函式
|
||||||
private suspend fun safeApiCall(call: suspend () -> Response<Unit>): Result<Unit> {
|
private suspend fun safeApiCall(call: suspend () -> Response<Unit>): Result<Unit> {
|
||||||
return try {
|
return try {
|
||||||
val response = call()
|
val response = call()
|
||||||
if (response.isSuccessful) Result.success(Unit)
|
if (response.isSuccessful) {
|
||||||
else Result.failure(Exception("API 錯誤: ${response.code()}"))
|
Log.d("API_DEBUG", "請求成功: ${response.raw().request.url}")
|
||||||
|
Result.success(Unit)
|
||||||
|
} else {
|
||||||
|
val errorBody = response.errorBody()?.string()
|
||||||
|
val errorCode = response.code()
|
||||||
|
val errorMessage = "API 錯誤 $errorCode: $errorBody"
|
||||||
|
Log.e("API_DEBUG", errorMessage)
|
||||||
|
Result.failure(Exception(errorMessage))
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("API_DEBUG", "連線或執行異常: ${e.message}", e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//add friends
|
// 搜尋使用者
|
||||||
suspend fun sendFriendRequest(friendId: String): Result<Unit> {
|
suspend fun searchUsers(keyword: String): Result<List<UserDetail>> {
|
||||||
return safeApiCall {
|
return try {
|
||||||
// 確保這裡的參數名稱與 Data Class 定義的一模一樣
|
val response = apiService.searchUsers(keyword)
|
||||||
val request = AddFriendRequest(friendId = friendId)
|
|
||||||
apiService.sendFriendRequest(request)
|
// 這裡做一個「結構轉換」
|
||||||
|
// 假設 additionalProp1 裡面裝的是我們想要的資料
|
||||||
|
// 如果 response.additionalProp1 是 Map,我們可以取其 Values 轉成 List
|
||||||
|
val userList = response.additionalProp1.values.toList()
|
||||||
|
|
||||||
|
Result.success(userList)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("API_DEBUG", "搜尋失敗: ${e.message}")
|
||||||
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ fun Message_contact_list(
|
||||||
) {
|
) {
|
||||||
// 統一觀察 uiState 即可,避免觀察多個 Flow 導致狀態不同步
|
// 統一觀察 uiState 即可,避免觀察多個 Flow 導致狀態不同步
|
||||||
val uiState by contactListViewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by contactListViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
// 判斷目前是否處於「搜尋模式」
|
||||||
|
val isSearching = uiState.searchQuery.isNotBlank()
|
||||||
val friends = uiState.friends // 直接從 uiState 裡面取
|
val friends = uiState.friends // 直接從 uiState 裡面取
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.example.login_v3.data.api.api_class.Friend
|
import com.example.login_v3.data.api.api_class.Friend
|
||||||
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
import com.example.login_v3.data.api.api_class.PendingFriendApiModel
|
||||||
|
import com.example.login_v3.data.api.api_class.UserDetail
|
||||||
import com.example.login_v3.data.repository.basic.FriendsRepository
|
import com.example.login_v3.data.repository.basic.FriendsRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
@ -19,6 +22,9 @@ data class ContactUiState(
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val friends: List<Friend> = emptyList(),
|
val friends: List<Friend> = emptyList(),
|
||||||
val pendingRequests: List<PendingFriendApiModel> = emptyList(),
|
val pendingRequests: List<PendingFriendApiModel> = emptyList(),
|
||||||
|
// 新增:搜尋結果與關鍵字
|
||||||
|
val searchResults: List<UserDetail> = emptyList(),
|
||||||
|
val searchQuery: String = "",
|
||||||
val error: String? = null
|
val error: String? = null
|
||||||
)
|
)
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
|
|
@ -112,4 +118,43 @@ class ContactListViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var searchJob: Job? = null
|
||||||
|
|
||||||
|
fun onSearchQueryChanged(newQuery: String) {
|
||||||
|
// 更新 UI 上的輸入文字
|
||||||
|
_uiState.update { it.copy(searchQuery = newQuery) }
|
||||||
|
|
||||||
|
// 如果關鍵字為空,清空搜尋結果並返回
|
||||||
|
if (newQuery.isBlank()) {
|
||||||
|
_uiState.update { it.copy(searchResults = emptyList()) }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 防抖處理 (Debounce) ---
|
||||||
|
searchJob?.cancel() // 取消前一個還在跑的搜尋
|
||||||
|
searchJob = viewModelScope.launch {
|
||||||
|
delay(500) // 使用者停頓 0.5 秒後才真正發出請求
|
||||||
|
performSearch(newQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun performSearch(query: String) {
|
||||||
|
_uiState.update { it.copy(isLoading = true) }
|
||||||
|
|
||||||
|
val result = repository.searchUsers(query)
|
||||||
|
|
||||||
|
result.onSuccess { users ->
|
||||||
|
_uiState.update { it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
searchResults = users,
|
||||||
|
error = null
|
||||||
|
)}
|
||||||
|
}.onFailure { error ->
|
||||||
|
_uiState.update { it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
error = "搜尋失敗: ${error.message}"
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue