auth to server-V80

This commit is contained in:
Eric85710 2026-05-11 15:22:56 +08:00
parent 64fc6e7a6f
commit 2ec5028b3e
4 changed files with 75 additions and 48 deletions

View file

@ -77,6 +77,6 @@ interface TecnologiaApi {
@GET("api/users/search") @GET("api/users/search")
suspend fun searchUsers( suspend fun searchUsers(
@Query("q") query: String @Query("q") query: String
): UserSearchResponse ): Response<UserSearchResponse>
} }

View file

@ -85,18 +85,29 @@ data class AddFriendRequest(
) )
//user search //user search
@JsonClass(generateAdapter = true)
data class UserSearchResponse( data class UserSearchResponse(
val count: Int, val count: Int,
val users: List<UserDetail> val users: List<UserDetail>
) )
@JsonClass(generateAdapter = true) // 內部類別也一定要加!
data class UserDetail( data class UserDetail(
val id: String, val id: String,
val username: String, val username: String,
val display_name: String,
val avatar_url: String?, // 考慮到可能是 null,建議加上 ? @Json(name = "display_name") // 這樣你的 Kotlin 代碼可以用漂亮的 displayName
val banner_url: String?, val displayName: String,
@Json(name = "avatar_url")
val avatarUrl: String?,
@Json(name = "banner_url")
val bannerUrl: String?,
val bio: String?, val bio: String?,
val status: String?, val status: String?,
val created_at: String
@Json(name = "created_at")
val createdAt: String
) )

View file

@ -6,6 +6,7 @@ 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 com.example.login_v3.data.api.api_class.UserDetail
import com.example.login_v3.data.api.api_class.UserSearchResponse
import retrofit2.Response import retrofit2.Response
import javax.inject.Inject import javax.inject.Inject
@ -58,6 +59,34 @@ class FriendsRepository @Inject constructor(
return result return result
} }
//search user
suspend fun searchUsers(query: String): Result<UserSearchResponse> {
return safeApiCallWithData {
apiService.searchUsers(query) // 1. 使用 apiService 2. 確保 apiService 定義回傳 Response<UserSearchResponse>
}
}
// 支援「回傳資料」的通用封裝 for search user
private suspend fun <T> safeApiCallWithData(call: suspend () -> Response<T>): Result<T> {
return try {
val response = call()
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
Result.success(body)
} else {
Result.failure(Exception("回傳資料為空"))
}
} else {
val errorMsg = "API 錯誤 ${response.code()}: ${response.errorBody()?.string()}"
Result.failure(Exception(errorMsg))
}
} catch (e: Exception) {
Result.failure(e)
}
}
// 2. 確保「全類別」只有這一個 safeApiCall 函式 // 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 {
@ -78,21 +107,4 @@ class FriendsRepository @Inject constructor(
} }
} }
// 搜尋使用者
suspend fun searchUsers(keyword: String): Result<List<UserDetail>> {
return try {
val response = apiService.searchUsers(keyword)
// 這裡做一個「結構轉換」
// 假設 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)
}
}
} }

View file

@ -119,42 +119,46 @@ class ContactListViewModel @Inject constructor(
} }
} }
private var searchJob: Job? = null
//search user
fun onSearchQueryChanged(newQuery: String) { fun onSearchQueryChanged(newQuery: String) {
// 更新 UI 上的輸入文字 // 1. 即時更新文字輸入,確保 UI TextField 顯示順暢
_uiState.update { it.copy(searchQuery = newQuery) } _uiState.update { it.copy(searchQuery = newQuery) }
}
// 如果關鍵字為空,清空搜尋結果並返回 fun performSearch() {
if (newQuery.isBlank()) { val query = _uiState.value.searchQuery
if (query.isBlank()) {
// 如果關鍵字為空,清空搜尋結果
_uiState.update { it.copy(searchResults = emptyList()) } _uiState.update { it.copy(searchResults = emptyList()) }
return return
} }
// --- 防抖處理 (Debounce) --- viewModelScope.launch {
searchJob?.cancel() // 取消前一個還在跑的搜尋 // 2. 顯示 Loading
searchJob = viewModelScope.launch { _uiState.update { it.copy(isLoading = true, error = null) }
delay(500) // 使用者停頓 0.5 秒後才真正發出請求
performSearch(newQuery)
}
}
private suspend fun performSearch(query: String) {
_uiState.update { it.copy(isLoading = true) }
// 3. 呼叫 Repository
val result = repository.searchUsers(query) val result = repository.searchUsers(query)
result.onSuccess { users -> // 4. 處理結果
_uiState.update { it.copy( result.onSuccess { response ->
_uiState.update {
it.copy(
isLoading = false, isLoading = false,
searchResults = users, searchResults = response.users // UserSearchResponse 裡的列表
error = null )
)} }
}.onFailure { error -> }.onFailure { error ->
_uiState.update { it.copy( _uiState.update {
it.copy(
isLoading = false, isLoading = false,
error = "搜尋失敗: ${error.message}" error = "搜尋失敗:${error.message}",
)} searchResults = emptyList() // 失敗時清空列表
)
}
}
} }
} }
} }