refactor: 重构网络请求

1.使用统一返回格式
2.使用DialogFragment替代原来的dialog
This commit is contained in:
糕小菜 2024-11-24 09:07:16 +08:00
parent a07711d993
commit c45acb5bda
38 changed files with 1004 additions and 751 deletions

View File

@ -80,7 +80,6 @@ dependencies {
implementation(libs.okhttp3.logging.interceptor)
// implementation(libs.therouter)
// ksp(libs.therouter.ksp)
}

View File

@ -1,12 +0,0 @@
package com.kaixed.kchat.model.response.register
import kotlinx.serialization.Serializable
@Serializable
data class Data(
val avatarUrl: String,
val nickname: String,
val signature: String,
val status: String?,
val username: String
)

View File

@ -4,7 +4,9 @@ import kotlinx.serialization.Serializable
@Serializable
data class Register(
val code: String,
val `data`: Data?,
val msg: String
val avatarUrl: String,
val nickname: String,
val signature: String,
val status: String?,
val username: String
)

View File

@ -0,0 +1,49 @@
package com.kaixed.kchat.model.search
import android.os.Parcel
import android.os.Parcelable
import kotlinx.serialization.Serializable
/**
* @Author: kaixed
* @Date: 2024/9/22 22:58
*/
@Serializable
data class SearchUser(
val username: String,
val nickname: String,
val signature: String,
val avatarUrl: String,
val status: String?,
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(username)
parcel.writeString(nickname)
parcel.writeString(signature)
parcel.writeString(avatarUrl)
parcel.writeString(status)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<SearchUser> {
override fun createFromParcel(parcel: Parcel): SearchUser {
return SearchUser(parcel)
}
override fun newArray(size: Int): Array<SearchUser?> {
return arrayOfNulls(size)
}
}
}

View File

@ -0,0 +1,17 @@
package com.kaixed.kchat.network
/**
* @Author: kaixed
* @Date: 2024/11/23 13:25
*/
data class ApiResponse<T>(
val code: String,
val msg: String,
val `data`: T?
) : BaseResponse<T?>() {
override fun isSuccess(): Boolean = code == "200"
override fun getResponseCode() = code
override fun getResponseData() = data
override fun getResponseMsg() = msg
}

View File

@ -0,0 +1,12 @@
package com.kaixed.kchat.network
abstract class BaseResponse<T> {
abstract fun isSuccess(): Boolean
abstract fun getResponseData(): T
abstract fun getResponseCode(): String
abstract fun getResponseMsg(): String
}

View File

@ -1,11 +0,0 @@
package com.kaixed.kchat.network
/**
* @Author: kaixed
* @Date: 2024/11/6 22:42
*/
sealed class NetResult<out T> {
data class Loading<out T>(val message: String = "加载中...") : NetResult<T>()
data class Success<out T>(val data: T) : NetResult<T>()
data class Error<out T>(val exception: Throwable) : NetResult<T>()
}

View File

@ -1,60 +0,0 @@
package com.kaixed.kchat.network
import androidx.lifecycle.MutableLiveData
import kotlinx.serialization.KSerializer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import okio.IOException
/**
* @Author: kaixed
* @Date: 2024/11/6 22:54
*/
object NetworkHelper {
fun <T> makeRequest(
url: String,
requestBody: Any? = null,
method: String = "POST",
serializer: KSerializer<T>
): MutableLiveData<NetResult<T>> {
val netResultLiveData = MutableLiveData<NetResult<T>>()
netResultLiveData.postValue(NetResult.Loading())
val callback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
netResultLiveData.postValue(NetResult.Error(e))
}
override fun onResponse(call: Call, response: Response) {
response.body?.string()?.let { responseBody ->
try {
val responseData = Json.decodeFromString(serializer, responseBody)
netResultLiveData.postValue(NetResult.Success(responseData))
} catch (e: Exception) {
netResultLiveData.postValue(NetResult.Error(e))
}
}
}
}
when (method) {
"POST" -> {
val json = Json.encodeToString(requestBody)
NetworkRequest().postAsync(url, json, callback)
}
"GET" -> {
NetworkRequest().getAsync(url, callback)
}
else -> {
netResultLiveData.postValue(NetResult.Error(IllegalArgumentException("不支持的请求方法")))
}
}
return netResultLiveData
}
}

View File

@ -1,76 +0,0 @@
package com.kaixed.kchat.network
import android.util.Log
import com.kaixed.kchat.network.OkhttpHelper.getInstance
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
/**
* @Author: kaixed
* @Date: 2024/10/14 13:52
*/
class NetworkRequest {
companion object {
const val TAG = "NetworkRequest"
}
private val client: OkHttpClient = getInstance()
fun postAsync(url: String, formBody: RequestBody, callback: Callback) {
val request = Request.Builder()
.url(url)
.post(formBody)
.build()
val call = client.newCall(request)
call.enqueue(callback)
}
fun postAsync(url: String, json: String, callback: Callback) {
val requestBody = json.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url(url)
.post(requestBody)
.build()
val call = client.newCall(request)
call.enqueue(callback)
}
fun postAsync(url: String, multipartBody: MultipartBody, callback: Callback) {
val contentType = "multipart/form-data; boundary=" + multipartBody.boundary
Log.d("haha", contentType)
val request = Request.Builder()
.url(url)
.post(multipartBody)
.addHeader("Connection", "keep-alive")
.addHeader("Content-Type", contentType)
.build()
val call = client.newCall(request)
call.enqueue(callback)
}
fun getAsync(url: String, callback: Callback) {
val request = Request.Builder()
.url(url)
.get()
.build()
val call = client.newCall(request)
call.enqueue(callback)
}
}

View File

@ -1,5 +1,6 @@
package com.kaixed.kchat.network
import com.kaixed.kchat.network.interceptor.SignInterceptor
import com.kaixed.kchat.network.service.ContactService
import com.kaixed.kchat.network.service.UserApiService
import okhttp3.OkHttpClient
@ -14,6 +15,7 @@ import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
private const val BASE_URL = "https://app.kaixed.com/kchat/"
// private const val BASE_URL = "http://192.168.156.209:6196/"
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
@ -21,6 +23,7 @@ object RetrofitClient {
private val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
// .addInterceptor(SignInterceptor()) // 添加签名拦截器
.build()

View File

@ -0,0 +1,115 @@
package com.kaixed.kchat.network
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.kaixed.kchat.model.request.UserRequest
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okio.Buffer
import java.io.IOException
import java.security.MessageDigest
object SignUtil {
// 生成签名
fun generateSign(request: Request, timestamp: String): String {
val secretKey = "your_secret_key"
val stringBuilder = StringBuilder("timestamp=$timestamp")
// 根据请求类型处理 GET 或 POST 请求
when (request.method) {
"GET" -> appendUrlParams(request, stringBuilder)
"POST" -> appendPostParams(request, stringBuilder)
}
// 拼接密钥并生成签名
stringBuilder.append(secretKey)
println(stringBuilder.toString())
return md5(stringBuilder.toString())
}
// 处理 GET 请求 URL 参数
private fun appendUrlParams(request: Request, stringBuilder: StringBuilder) {
val url = request.url
url.queryParameterNames.forEach { name ->
val value = url.queryParameter(name)
stringBuilder.append("&$name=$value")
}
}
// 处理 POST 请求参数,包括表单数据和 JSON 数据
private fun appendPostParams(request: Request, stringBuilder: StringBuilder) {
val body = request.body
body?.let {
when (body.contentType()?.subtype) {
"x-www-form-urlencoded" -> appendFormParams(it, stringBuilder)
"json" -> appendJsonBody(it, stringBuilder)
}
}
}
// 处理表单数据
private fun appendFormParams(body: okhttp3.RequestBody, stringBuilder: StringBuilder) {
val formBody = body as okhttp3.FormBody
val sortedParams = (0 until formBody.size)
.map { Pair(formBody.name(it), formBody.value(it)) }
.sortedBy { it.first }
sortedParams.forEach { (name, value) ->
stringBuilder.append("&$name=$value")
}
}
// 处理 JSON 数据并排序
private fun appendJsonBody(body: okhttp3.RequestBody, stringBuilder: StringBuilder) {
val jsonBody = getRequestBodyAsJsonObject(body)
jsonBody?.let {
val sortedJson = sortJson(it)
stringBuilder.append("&body=$sortedJson")
}
}
// 读取 RequestBody 并解析为 JsonObject
private fun getRequestBodyAsJsonObject(body: okhttp3.RequestBody): JsonObject? {
val buffer = Buffer()
try {
body.writeTo(buffer)
val bodyString = buffer.readUtf8()
return JsonParser.parseString(bodyString).asJsonObject
} catch (e: IOException) {
e.printStackTrace()
return null
}
}
// 排序 JSON 对象
private fun sortJson(jsonObject: JsonObject): String {
return jsonObject.keySet()
.sorted()
.joinToString(",", "{", "}") { key -> "\"$key\":${jsonObject[key]}" }
}
// MD5 加密函数
private fun md5(input: String): String {
val digest = MessageDigest.getInstance("MD5")
val bytes = digest.digest(input.toByteArray())
return bytes.joinToString("") { "%02x".format(it) }
}
@JvmStatic
fun main(args: Array<String>) {
val request = Request.Builder()
.url("https://app.kaixed.com/kchat/users/login/username")
.post(
Gson().toJson(UserRequest("username", "password"))
.toRequestBody("application/json".toMediaType())
)
.build()
val timestamp = System.currentTimeMillis().toString()
val sign = generateSign(request, timestamp)
println("sign: $sign")
}
}

View File

@ -0,0 +1,37 @@
package com.kaixed.kchat.network.interceptor
import com.kaixed.kchat.network.SignUtil.generateSign
import okhttp3.Interceptor
import okhttp3.Response
class SignInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val urlPath = originalRequest.url.encodedPath
val skipSignPaths = listOf(
"/users/avatar",
)
// 检查是否需要跳过签名
if (skipSignPaths.any { urlPath.endsWith(it) }) {
// 直接放行请求,不做签名处理
return chain.proceed(originalRequest)
}
// 获取当前时间戳
val timestamp = System.currentTimeMillis().toString()
// 生成请求的签名
val sign = generateSign(originalRequest, timestamp)
// 创建新的请求,加入 sign 和 timestamp
val newRequest = originalRequest.newBuilder()
.addHeader("sign", sign)
.addHeader("timestamp", timestamp)
.build()
return chain.proceed(newRequest)
}
}

View File

@ -1,12 +1,9 @@
package com.kaixed.kchat.network.service
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.response.ApplyFriend
import com.kaixed.kchat.model.response.friend.DeleteContact
import com.kaixed.kchat.model.response.friend.FriendList
import com.kaixed.kchat.model.response.friend.SearchFriends
import retrofit2.Response
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User
import com.kaixed.kchat.network.ApiResponse
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
@ -20,7 +17,7 @@ interface ContactService {
@POST("friend/request/list")
suspend fun getContactRequestList(
@Field("userId") username: String
): Response<ContactRequestResponse>
): ApiResponse<List<FriendRequestItem>?>
// 接受联系人请求
@FormUrlEncoded
@ -29,7 +26,7 @@ interface ContactService {
@Field("requestId") contactId: String,
@Field("receiverId") username: String,
@Field("remark") remark: String
): Response<AcceptContactRequest>
): ApiResponse<Contact?>
// 添加联系人
@FormUrlEncoded
@ -38,26 +35,26 @@ interface ContactService {
@Field("senderId") senderId: String,
@Field("receiverId") receiverId: String,
@Field("message") message: String
): Response<ApplyFriend>
): ApiResponse<String>
// 搜索联系人
@GET("users/{username}")
suspend fun searchContact(
@Path("username") username: String
): Response<SearchFriends>
): ApiResponse<User?>
// 获取联系人列表
@FormUrlEncoded
@POST("friend/list")
suspend fun getContactList(
@Field("userId") username: String
): Response<FriendList>
): ApiResponse<List<Contact>?>
// 删除好友
// 删除联系人
@FormUrlEncoded
@POST("friend/delete")
suspend fun deleteContact(
@Field("userId") username: String,
@Field("contactId") contactId: String,
): Response<DeleteContact>
): ApiResponse<String?>
}

View File

@ -1,13 +1,15 @@
package com.kaixed.kchat.network.service
import com.kaixed.kchat.data.objectbox.entity.UserInfo
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
import com.kaixed.kchat.model.response.search.User
import com.kaixed.kchat.model.response.user.ChangeNickname
import com.kaixed.kchat.model.response.user.UploadAvatar
import com.kaixed.kchat.model.search.SearchUser
import com.kaixed.kchat.network.ApiResponse
import okhttp3.MultipartBody
import retrofit2.Response
import retrofit2.http.Body
@ -29,7 +31,7 @@ interface UserApiService {
@POST("users/register")
suspend fun register(
@Body registerRequest: RegisterRequest
): Response<Register>
): ApiResponse<Register>
// 登录接口(根据用户名登录)
@FormUrlEncoded
@ -37,7 +39,7 @@ interface UserApiService {
suspend fun loginByUsername(
@Field("username") username: String,
@Field("password") password: String
): Response<Login>
): ApiResponse<UserInfo>
// 登录接口(根据电话登录)
@FormUrlEncoded
@ -45,25 +47,25 @@ interface UserApiService {
suspend fun loginByTelephone(
@Field("telephone") telephone: String,
@Field("password") password: String
): Response<Login>
): ApiResponse<UserInfo>
// 获取用户列表
@GET("userList/{username}")
suspend fun getUserListByNickname(
@Path("username") username: String
): Response<UserList>
): ApiResponse<List<User>>
// 获取用户信息
@GET("users/{username}")
suspend fun getUserInfo(
@Path("username") username: String
): Response<SearchFriends>
): ApiResponse<SearchUser>
// 更改昵称接口
@POST("users/info")
suspend fun changeNickname(
@Body userRequest: UserRequest
): Response<ChangeNickname>
): ApiResponse<String>
// 上传头像接口
@Multipart
@ -71,5 +73,5 @@ interface UserApiService {
suspend fun uploadAvatar(
@Part("username") username: String,
@Part file: MultipartBody.Part
): Response<UploadAvatar>
): ApiResponse<String>
}

View File

@ -1,20 +1,15 @@
package com.kaixed.kchat.repository
import android.util.Log
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.data.objectbox.entity.Contact_
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.response.ApplyFriend
import com.kaixed.kchat.model.response.friend.DeleteContact
import com.kaixed.kchat.model.response.friend.FriendList
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User
import com.kaixed.kchat.network.RetrofitClient
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.Pinyin4jUtil
import com.kaixed.kchat.utils.handle.ContactUtil
import io.objectbox.Box
import okhttp3.ResponseBody.Companion.toResponseBody
import retrofit2.Response
class ContactRepository {
@ -25,12 +20,19 @@ class ContactRepository {
}
// 获取联系人请求列表
suspend fun getContactRequestList(username: String): Response<ContactRequestResponse> {
suspend fun getContactRequestList(username: String): Result<List<FriendRequestItem>?> {
return try {
contactApiService.getContactRequestList(username)
val response = contactApiService.getContactRequestList(username)
if (response.isSuccess()) {
val searchUser = response.getResponseData()
searchUser?.let {
Result.success(searchUser)
} ?: Result.failure(Exception("没有好友申请"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Log.e("ContactRepository", "Get Contact Request List failed: ${e.message}")
Response.error(500, "".toResponseBody()) // 返回一个空的错误 Response
Result.failure(e)
}
}
@ -39,12 +41,20 @@ class ContactRepository {
username: String,
contactId: String,
remark: String
): Response<AcceptContactRequest> {
): Result<Contact?> {
return try {
contactApiService.acceptContactRequest(contactId, username, remark)
val response = contactApiService.acceptContactRequest(contactId, username, remark)
if (response.isSuccess()) {
val searchUser = response.getResponseData()
searchUser?.let {
ContactUtil.handleContact(searchUser)
Result.success(searchUser)
} ?: Result.failure(Exception("添加好友失败"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Log.e("ContactRepository", "Accept Contact Request failed: ${e.message}")
Response.error(500, "".toResponseBody())
Result.failure(e)
}
}
@ -52,47 +62,121 @@ class ContactRepository {
suspend fun deleteContact(
username: String,
contactId: String,
): Response<DeleteContact> {
try {
contactBox.query(Contact_.username.equal(contactId))
return contactApiService.deleteContact(username, contactId)
): Result<String?> {
return try {
val response = contactApiService.deleteContact(username, contactId)
if (response.isSuccess()) {
val searchUser = response.getResponseData()
searchUser?.let {
val con =
contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
contactBox.remove(con!!)
Result.success(searchUser)
} ?: Result.failure(Exception("删除好友失败"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Log.e("ContactRepository", "Delete Contact failed: ${e.message}")
return Response.error(500, "".toResponseBody())
Result.failure(e)
}
}
// 添加联系人
suspend fun addContact(contactId: String, message: String): Response<ApplyFriend> {
suspend fun addContact(contactId: String, message: String): Result<String> {
return try {
contactApiService.addContact(
val response = contactApiService.addContact(
senderId = contactId,
receiverId = getUsername(),
message = message
)
if (response.isSuccess()) {
val searchUser = response.getResponseData()
searchUser?.let {
Result.success(searchUser)
} ?: Result.failure(Exception("添加联系人失败"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Log.e("ContactRepository", "Add Contact failed: ${e.message}")
Response.error(500, "".toResponseBody())
Result.failure(e)
}
}
// 搜索联系人
suspend fun searchContact(username: String): Response<SearchFriends> {
suspend fun searchContact(username: String): Result<User?> {
return try {
contactApiService.searchContact(username)
val response = contactApiService.searchContact(username)
if (response.isSuccess()) {
val searchUser = response.getResponseData()
searchUser?.let {
Result.success(searchUser)
} ?: Result.failure(Exception("没有找到用户"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Log.e("ContactRepository", "Search Contact failed: ${e.message}")
Response.error(500, "".toResponseBody())
Result.failure(e)
}
}
// 获取联系人列表
suspend fun getContactList(username: String): Response<FriendList> {
suspend fun getContactList(username: String): Result<List<Contact>?> {
return try {
contactApiService.getContactList(username)
val response = contactApiService.getContactList(username)
if (response.isSuccess()) {
val searchUsers = response.getResponseData()
searchUsers?.let {
val uiFriendList = searchUsers?.map { networkItem ->
mapToFriendItem(networkItem) // 数据转换
}?.sortedWith { f1, f2 ->
val str1 = f1.remarkquanpin ?: f1.quanpin ?: f1.nickname
val str2 = f2.remarkquanpin ?: f2.quanpin ?: f2.nickname
val type1 = when {
str1.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str1.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
val type2 = when {
str2.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str2.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
// 比较类型,如果相同再按字母升序排序
if (type1 != type2) {
type1 - type2 // 按类型排序
} else {
str1.compareTo(str2) // 如果类型相同,按字母顺序排序
}
}
// 设置是否显示分组头
uiFriendList?.forEachIndexed { index, item ->
item.showHeader =
(index == 0 || item.quanpin?.get(index) != uiFriendList[index - 1].quanpin?.get(
index - 1
))
}
Result.success(searchUsers)
} ?: Result.failure(Exception("当前没有好友"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Log.e("ContactRepository", "Get Contact List failed: ${e.message}")
Response.error(500, "".toResponseBody())
Result.failure(e)
}
}
private fun mapToFriendItem(response: Contact): Contact {
val pinyin = Pinyin4jUtil.toPinyin(response.nickname)
response.remark?.let {
response.remarkquanpin = Pinyin4jUtil.toPinyin(it)
}
response.quanpin = pinyin
return response
}
}

View File

@ -0,0 +1,103 @@
package com.kaixed.kchat.repository
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.UserInfo
import com.kaixed.kchat.data.objectbox.entity.UserInfo_
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.network.RetrofitClient
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.tencent.mmkv.MMKV
import io.objectbox.Box
/**
* @Author: kaixed
* @Date: 2024/11/23 14:15
*/
class UserAuthRepository {
private val userApiService = RetrofitClient.userApiService
private val userInfoBox: Box<UserInfo> by lazy { get().boxFor(UserInfo::class.java) }
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
// 用户注册
suspend fun register(registerRequest: RegisterRequest): Result<Register> {
return try {
val response = userApiService.register(registerRequest)
if (response.isSuccess()) {
val register = response.getResponseData()
register?.let {
val userInfo = UserInfo(
username = register.username,
nickname = register.nickname,
avatarUrl = "",
signature = "",
telephone = registerRequest.telephone
)
userInfoBox.put(userInfo)
Result.success(register)
} ?: Result.failure(Exception("注册成功,但未返回用户数据"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Result.failure(e)
}
}
// 登录方法
suspend fun loginByUsername(username: String, password: String): Result<UserInfo> {
return try {
val response = userApiService.loginByUsername(username, password)
if (response.isSuccess()) {
val userInfo = response.getResponseData()
if (userInfo != null) {
updateDb(userInfo)
Result.success(userInfo)
} else {
Result.failure(Exception("登录成功,但未返回用户数据"))
}
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Result.failure(e)
}
}
private fun updateDb(userInfo: UserInfo) {
mmkv.apply {
encode("username", userInfo.username)
encode("nickname", userInfo.nickname)
encode("avatarUrl", userInfo.avatarUrl)
}
MMKV.defaultMMKV().encode("userLoginStatus", true)
val user = userInfoBox
.query(UserInfo_.username.equal(userInfo.username))
.build()
.findFirst()
if (user == null) {
userInfoBox.put(userInfo)
}
}
suspend fun loginByTelephone(telephone: String, password: String): Result<UserInfo> {
return try {
val response = userApiService.loginByTelephone(telephone, password)
if (response.isSuccess()) {
val userInfo = response.getResponseData()
userInfo?.let {
updateDb(userInfo)
Result.success(userInfo)
} ?: Result.failure(Exception("登录成功,但未返回用户数据"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}

View File

@ -0,0 +1,98 @@
package com.kaixed.kchat.repository
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.UserInfo
import com.kaixed.kchat.data.objectbox.entity.UserInfo_
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.model.search.SearchUser
import com.kaixed.kchat.network.RetrofitClient
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.Constants.NICKNAME_KEY
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.tencent.mmkv.MMKV
import okhttp3.MultipartBody
/**
* @Author: kaixed
* @Date: 2024/11/23 14:15
*/
class UserProfileRepository {
private val userApiService = RetrofitClient.userApiService
// 获取用户信息
suspend fun getUserInfo(username: String): Result<SearchUser> {
return try {
val response = userApiService.getUserInfo(username)
if (response.isSuccess()) {
val searchUser = response.getResponseData()
searchUser?.let {
Result.success(searchUser)
} ?: Result.failure(Exception("用户不存在"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Result.failure(e)
}
}
// 修改昵称
suspend fun changeNickname(userRequest: UserRequest): Result<Boolean> {
return try {
val response = userApiService.changeNickname(userRequest)
if (response.isSuccess()) {
updateNickname(response.getResponseData().toString())
Result.success(true)
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Result.failure(e)
}
}
// 上传头像
suspend fun uploadAvatar(file: MultipartBody.Part, username: String): Result<String> {
return try {
val response = userApiService.uploadAvatar(username, file)
if (response.isSuccess()) {
val data = response.getResponseData()
data?.let {
updateDb(it)
Result.success(it)
} ?: Result.failure(Exception("请求成功,但返回数据为空"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Result.failure(e)
}
}
private fun updateNickname(newNickname: String) {
val userInfoBox = get().boxFor(UserInfo::class.java)
val user = userInfoBox
.query(UserInfo_.username.equal(getUsername()))
.build()
.findFirst()
MMKV.mmkvWithID(MMKV_USER_SESSION).putString(NICKNAME_KEY, newNickname)
user?.nickname = newNickname
if (user != null) {
userInfoBox.put(user)
}
}
private fun updateDb(url: String) {
val userInfoBox = get().boxFor(UserInfo::class.java)
val userInfo =
userInfoBox.query(UserInfo_.username.equal(getUsername())).build().findFirst()
if (userInfo != null) {
userInfo.avatarUrl = url
userInfoBox.put(userInfo)
}
}
}

View File

@ -1,94 +0,0 @@
package com.kaixed.kchat.repository
import android.util.Log
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
import com.kaixed.kchat.network.RetrofitClient
import okhttp3.MultipartBody
import okhttp3.ResponseBody.Companion.toResponseBody
import retrofit2.Response
class UserRepository {
private val userApiService = RetrofitClient.userApiService
// 用户注册
suspend fun register(registerRequest: RegisterRequest): Response<Register> {
return try {
userApiService.register(registerRequest)
} catch (e: Exception) {
Log.e("UserRepository", "Register Failed: ${e.message}", e)
Response.error(500, "".toResponseBody())
}
}
// 登录请求(支持用户名或手机号)
suspend fun login(
username: String?,
password: String,
loginByUsername: Boolean
): Response<Login> {
return try {
if (loginByUsername) {
// 用户名登录
userApiService.loginByUsername(username ?: "", password)
} else {
// 电话号登录
userApiService.loginByTelephone(username ?: "", password)
}
} catch (e: Exception) {
Log.e("UserRepository", "Login Failed: ${e.message}", e)
Response.error(500, "".toResponseBody()) // 返回一个空的错误 Response
}
}
// 获取用户列表
suspend fun getUserList(username: String): Response<UserList> {
return try {
userApiService.getUserListByNickname(username)
} catch (e: Exception) {
Log.e("UserRepository", "Get User Info Failed: ${e.message}", e)
Response.error(500, "".toResponseBody())
}
}
// 获取用户信息
suspend fun getUserInfo(username: String): Response<SearchFriends> {
return try {
userApiService.getUserInfo(username)
} catch (e: Exception) {
Log.e("UserRepository", "Get User List Failed: ${e.message}", e)
Response.error(500, "".toResponseBody())
}
}
// 修改昵称
suspend fun changeNickname(userRequest: UserRequest): Response<Boolean> {
return try {
val response = userApiService.changeNickname(userRequest)
Response.success(response.isSuccessful)
} catch (e: Exception) {
Log.e("UserRepository", "Change Nickname Failed: ${e.message}", e)
Response.error(500, "".toResponseBody())
}
}
// 上传头像
suspend fun uploadAvatar(file: MultipartBody.Part, username: String): Response<String?> {
return try {
val response = userApiService.uploadAvatar(username, file)
if (response.isSuccessful) {
Response.success(response.body()?.data)
} else {
Response.success(null)
}
} catch (e: Exception) {
Log.e("UserRepository", "Upload Avatar Failed: ${e.message}", e)
Response.error(500, "".toResponseBody())
}
}
}

View File

@ -0,0 +1,30 @@
package com.kaixed.kchat.repository
import com.kaixed.kchat.model.response.search.User
import com.kaixed.kchat.network.RetrofitClient
/**
* @Author: kaixed
* @Date: 2024/11/23 14:16
*/
class UserSearchRepository {
private val userApiService = RetrofitClient.userApiService
// 获取用户列表
suspend fun getUserList(username: String): Result<List<User>> {
return try {
val response = userApiService.getUserListByNickname(username)
if (response.isSuccess()) {
val userList = response.getResponseData()
userList?.let {
Result.success(userList)
} ?: Result.failure(Exception("用户列表为空"))
} else {
Result.failure(Exception(response.getResponseMsg()))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}

View File

@ -1,7 +1,6 @@
package com.kaixed.kchat.ui.activity
import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import com.kaixed.kchat.databinding.ActivityApplyAddFriendBinding
@ -29,14 +28,13 @@ class ApplyAddFriendActivity : BaseActivity<ActivityApplyAddFriendBinding>() {
private fun sendContactRequest(contactId: String?) {
contactId?.let {
contactViewModel.addContactResponse
.observe(this) { value ->
runOnUiThread {
if (value?.code == "200") {
Toast.makeText(this, value.msg, Toast.LENGTH_SHORT).show()
contactViewModel.addContactResult
.observe(this) { result ->
result.onSuccess {
toast(result.getOrNull().toString())
finish()
}
}
result.onFailure { toast(it.message.toString()) }
}
contactViewModel.addContact(contactId, binding.etMessage.text.toString())
}

View File

@ -49,20 +49,15 @@ class ApproveContactRequestActivity : BaseActivity<ActivityApproveContactRequest
private fun setOnClickListener() {
binding.tvFinish.setOnClickListener {
acceptContactRequest(contactId!!)
acceptContactRequest(contactId!!, nickname!!)
}
}
private fun acceptContactRequest(contactId: String) {
contactViewModel.acceptContactRequestResponse
.observe(this) { value ->
value?.let {
if (value.code != "200") {
toast(it.msg)
} else {
value.data?.let { it1 ->
ContactUtil.handleContact(it1)
}
private fun acceptContactRequest(contactId: String, nickname: String) {
contactViewModel.acceptContactRequestResult
.observe(this) { result ->
result.onSuccess {
val stackBuilder: TaskStackBuilder = TaskStackBuilder.create(this)
stackBuilder.addNextIntentWithParentStack(
Intent(
@ -72,11 +67,15 @@ class ApproveContactRequestActivity : BaseActivity<ActivityApproveContactRequest
stackBuilder.addNextIntent(
Intent(
this, ChatActivity::class.java
).putExtra("friendId", contactId)
).apply {
putExtra("contactId", contactId)
putExtra("contactNickname", nickname)
}
)
stackBuilder.startActivities()
}
}
result.onFailure { toast(it.message.toString()) }
}
val remark = binding.etRemark.text.toString()

View File

@ -39,12 +39,16 @@ class ContactRequestListActivity : BaseActivity<ActivityContactRequestListBindin
}
private fun getItems() {
contactViewModel.contactRequestListResponse
.observe(this) { value ->
if (value != null) {
items.addAll(value.data.toMutableList())
contactViewModel.contactRequestListResult
.observe(this) { result ->
result.onSuccess {
items.addAll(result.getOrNull()?.toMutableList() ?: emptyList())
contactRequestListAdapter.notifyDataSetChanged()
}
result.onFailure {
toast(it.message.toString())
}
}
contactViewModel.getContactRequestList(getUsername())

View File

@ -1,31 +1,22 @@
package com.kaixed.kchat.ui.activity
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Window
import android.view.WindowManager
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.data.objectbox.entity.Contact_
import com.kaixed.kchat.databinding.ActivityDataSettingBinding
import com.kaixed.kchat.databinding.DialogDeleteContactBinding
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.ui.fragment.DeleteContactDialogFragment
import com.kaixed.kchat.ui.fragment.LoadingDialogFragment
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.WidgetUtil
import com.kaixed.kchat.viewmodel.ContactViewModel
import io.objectbox.Box
class DataSettingActivity : BaseActivity<ActivityDataSettingBinding>() {
class DataSettingActivity : BaseActivity<ActivityDataSettingBinding>(),
DeleteContactDialogFragment.OnContactDeletedListener {
private val contactViewModel by viewModels<ContactViewModel>()
@ -33,6 +24,8 @@ class DataSettingActivity : BaseActivity<ActivityDataSettingBinding>() {
private var contactId = ""
private val loadingDialogFragment by lazy { LoadingDialogFragment.newInstance("加载中...") }
override fun inflateBinding(): ActivityDataSettingBinding {
return ActivityDataSettingBinding.inflate(layoutInflater)
}
@ -40,11 +33,8 @@ class DataSettingActivity : BaseActivity<ActivityDataSettingBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityDataSettingBinding.inflate(layoutInflater)
setContentView(binding.root)
setOnClick()
handleIntent(intent)
}
@ -59,50 +49,35 @@ class DataSettingActivity : BaseActivity<ActivityDataSettingBinding>() {
}
binding.tvDelete.setOnClickListener {
val dialog = showDeleteDialog(this)
dialog.show()
val deleteDialogFragment = DeleteContactDialogFragment.newInstance(contactId)
deleteDialogFragment.show(supportFragmentManager, "DeleteContactDialogFragment")
}
}
private fun showDeleteDialog(context: Context): Dialog {
val binding = DialogDeleteContactBinding.inflate(LayoutInflater.from(context))
val dialog = Dialog(context)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(binding.root)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val layoutParams = WindowManager.LayoutParams().apply {
copyFrom(dialog.window?.attributes)
width = (context.resources.displayMetrics.widthPixels * 0.8).toInt()
gravity = Gravity.CENTER
}
dialog.window?.attributes = layoutParams
binding.tvCancel.setOnClickListener {
dialog.dismiss()
}
binding.tvDelete.setOnClickListener {
override fun onContactDeleted() {
deleteContact(getUsername(), contactId)
}
return dialog
private fun deleteContact(username: String, contactId: String) {
loadingDialogFragment.show(supportFragmentManager, "LoadingDialogFragment")
contactViewModel.deleteContactResult.observe(this) { result ->
result.onSuccess {
loadingDialogFragment.dismiss()
toast("删除成功")
val deleteIntent = Intent("com.kaixed.kchat.broadcast.UPDATE_CONTACT_LIST")
deleteIntent.putExtra("update", true)
LocalBroadcastManager.getInstance(this).sendBroadcast(deleteIntent)
val intent = Intent(this, MainActivity::class.java).apply {
setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
startActivity(intent)
}
private fun deleteContact(username: String, contactId: String) {
val dialog = WidgetUtil.showLoadingDialog(this, "删除中...")
dialog.show()
contactViewModel.deleteContact.observe(this) {
it?.let {
val con = contactBox.query(Contact_.username.equal(contactId)).build().findFirst()
contactBox.remove(con!!)
val handle = Handler(Looper.getMainLooper())
handle.postDelayed({
dialog.dismiss()
}, 1000)
toast("删除成功")
} ?: toast("删除失败")
result.onFailure { toast(it.message.toString()) }
}
contactViewModel.deleteContact(username, contactId)
}

View File

@ -32,9 +32,6 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
private val userViewModel: UserViewModel by viewModels()
private val userInfoBox: Box<UserInfo> by lazy { get().boxFor(UserInfo::class.java) }
private val mmkv by lazy { MMKV.mmkvWithID(MMKV_USER_SESSION) }
private var previousKeyboardHeight = 0
private var loginByUsername = false
@ -68,39 +65,18 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
if (username.isEmpty() || password.isEmpty()) {
toast("请输入${if (loginByUsername) "用户名" else "手机号"}或密码")
}
userViewModel.loginResponse.observe(this) { loginResult ->
loginResult?.let {
when (it.code) {
"A0201" -> toast("用户账户不存在")
"A0209" -> toast("用户密码错误")
else -> {
mmkv.apply {
encode("username", loginResult.data?.username)
encode("nickname", loginResult.data?.nickname)
encode("avatarUrl", loginResult.data?.avatarUrl)
}
MMKV.defaultMMKV().encode("userLoginStatus", true)
updateDb(loginResult.data!!)
userViewModel.loginResult.observe(this) { loginResult ->
loginResult.onSuccess {
navigateToMain()
}
loginResult.onFailure {
toast(it.message.toString())
}
} ?: toast("登录异常")
}
userViewModel.login(username, password, loginByUsername)
}
private fun updateDb(userInfo: UserInfo) {
CoroutineScope(Dispatchers.IO).launch {
val user = userInfoBox
.query(UserInfo_.username.equal(userInfo.username))
.build()
.findFirst()
if (user == null) {
userInfoBox.put(userInfo)
}
withContext(Dispatchers.Main) {
toast("登录成功")
}
if (loginByUsername) {
userViewModel.loginByUsername(username, password)
} else {
userViewModel.loginByTelephone(username, password)
}
}
@ -185,6 +161,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
}
private fun navigateToMain() {
toast("登录成功")
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()

View File

@ -21,6 +21,7 @@ import com.kaixed.kchat.ui.fragment.HomeFragment
import com.kaixed.kchat.ui.fragment.MineFragment
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.ConstantsUtil.isFirstLaunchApp
import com.kaixed.kchat.utils.Pinyin4jUtil
import com.kaixed.kchat.utils.WidgetUtil
import com.kaixed.kchat.viewmodel.ContactViewModel
import io.objectbox.Box
@ -76,8 +77,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), View.OnClickListener {
dialog.show()
delay(200)
contactViewModel.contactListResponse.observe(this@MainActivity) { contacts ->
contactBox.put(contacts ?: emptyList())
contactViewModel.contactListResult.observe(this@MainActivity) { result ->
result.onSuccess {
contactBox.put(it ?: emptyList())
}
}
contactViewModel.loadFriendList(getUsername())

View File

@ -81,12 +81,14 @@ class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
val dialog = WidgetUtil.showLoadingDialog(this, "正在上传")
dialog.show()
userViewModel.uploadAvatarResponse.observe(this) { value ->
value?.let {
userSessionMMKV.putString(AVATAR_URL, value)
updateDb(value, dialog)
userViewModel.uploadAvatarResult.observe(this) { result ->
result.onSuccess {
userSessionMMKV.putString(AVATAR_URL, it)
toast("上传成功")
binding.ciAvatar.setItemIcon(uri)
} ?: run {
}
result.onFailure {
toast("上传失败")
}
}
@ -100,16 +102,7 @@ class ProfileDetailActivity : BaseActivity<ActivityProfileDetailBinding>() {
return MultipartBody.Part.createFormData("file", file.name, requestFile)
}
private fun updateDb(url: String, dialog: Dialog) {
val userInfo =
userInfoBox.query(UserInfo_.username.equal(getUsername())).build().findFirst()
if (userInfo != null) {
userInfo.avatarUrl = url
userInfoBox.put(userInfo)
}
dialog.dismiss()
toast("上传成功")
}
private fun getPathFromUri(uri: Uri): String? {

View File

@ -28,7 +28,7 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
private var tvContinueEnable: Boolean = false
private val userViewmodel: UserViewModel by viewModels()
private val userViewModel: UserViewModel by viewModels()
private val userInfoBox: Box<UserInfo> by lazy { get().boxFor(UserInfo::class.java) }
@ -78,14 +78,15 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
signature: String,
telephone: String
) {
userViewmodel.registerResponse.observe(this) { registerResult ->
when (registerResult?.code) {
"A0111" -> toast("用户名已存在,请重新注册")
"200" -> registerResult.data?.username?.let {
onRegisterSuccess(it, nickname, telephone)
userViewModel.registerResult.observe(this) { result ->
result.onSuccess {
toast("注册成功,请前往登录界面登录")
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
else -> toast("系统服务器异常")
result.onFailure {
toast(it.message.toString())
}
}
@ -96,22 +97,7 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
signature = signature,
telephone = telephone
)
userViewmodel.register(registerRequest)
}
private fun onRegisterSuccess(username: String, nickname: String, telephone: String) {
val userInfo = UserInfo(
username = username,
nickname = nickname,
avatarUrl = "",
signature = "",
telephone = telephone
)
userInfoBox.put(userInfo)
toast("注册成功,请前往登录界面登录")
startActivity(Intent(this, LoginActivity::class.java))
finish()
userViewModel.register(registerRequest)
}

View File

@ -8,17 +8,13 @@ import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.UserInfo
import com.kaixed.kchat.data.objectbox.entity.UserInfo_
import com.kaixed.kchat.databinding.ActivityRenameBinding
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.ui.base.BaseActivity
import com.kaixed.kchat.utils.Constants.MMKV_USER_SESSION
import com.kaixed.kchat.utils.Constants.NICKNAME_KEY
import com.kaixed.kchat.utils.ConstantsUtil.getNickName
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.utils.WidgetUtil
import com.kaixed.kchat.viewmodel.UserViewModel
import com.tencent.mmkv.MMKV
import io.objectbox.Box
class RenameActivity : BaseActivity<ActivityRenameBinding>() {
@ -66,27 +62,19 @@ class RenameActivity : BaseActivity<ActivityRenameBinding>() {
binding.ctb.setBtnEnable(false)
val dialog = WidgetUtil.showLoadingDialog(this, "正在保存")
dialog.show()
userViewModel.changeNicknameResponse.observe(this) { result ->
if (result) {
val user = userInfoBox
.query(UserInfo_.username.equal(username))
.build()
.findFirst()
userViewModel.changeNicknameResult.observe(this) { result ->
MMKV.mmkvWithID(MMKV_USER_SESSION).putString(NICKNAME_KEY, newNickname)
user?.nickname = newNickname
if (user != null) {
userInfoBox.put(user)
}
result.onSuccess {
binding.etNickname.setText(newNickname)
updateSucceed = true
} else {
}
result.onFailure {
updateSucceed = false
binding.etNickname.setText(oldNickname)
}
binding.etNickname.setSelection(newNickname.length)
binding.etNickname.setSelection(binding.etNickname.text.length)
}
Handler(Looper.getMainLooper()).postDelayed({
dialog.dismiss()

View File

@ -90,24 +90,22 @@ class SearchFriendsActivity : BaseActivity<ActivitySearchFriendsBinding>() {
loadingDialog = showLoadingDialog(this)
val username = binding.etSearch.text.toString()
contactViewModel.searchContactResponse.observe(this) { value ->
contactViewModel.searchContactResult.observe(this) { result ->
loadingDialog.dismiss()
value?.let {
if (value.code == "200") {
value.data?.let {
userItem = value.data
result.onSuccess {
it?.let {
userItem = it
if (::userItem.isInitialized) {
setVisibility(true)
setContent()
}
} ?: run {
}
}
result.onFailure {
setVisibility(false)
binding.tvNothing.visibility = View.VISIBLE
}
}
}
}
contactViewModel.searchContact(username)
}

View File

@ -1,12 +1,15 @@
package com.kaixed.kchat.ui.fragment
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.databinding.FragmentContactBinding
@ -14,9 +17,7 @@ import com.kaixed.kchat.ui.adapter.FriendListAdapter
import com.kaixed.kchat.ui.base.BaseFragment
import com.kaixed.kchat.utils.ConstantsUtil.getUsername
import com.kaixed.kchat.viewmodel.ContactViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ContactFragment : BaseFragment<FragmentContactBinding>() {
@ -41,12 +42,25 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
return FragmentContactBinding.inflate(inflater, container, false)
}
private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val update = intent.getBooleanExtra("update", false) // 获取单一的 boolean 类型值
if (update) {
loadData()
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context = requireContext()
loadData()
loadFriendRequest()
val filter = IntentFilter("com.kaixed.kchat.broadcast.UPDATE_CONTACT_LIST")
LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(mReceiver, filter)
}
override fun onResume() {
@ -54,40 +68,50 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
loadFriendRequest()
}
override fun onStart() {
super.onStart()
loadFriendRequest()
}
override fun onDestroy() {
super.onDestroy()
LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver);
}
private fun loadFriendRequest() {
contactViewModel.contactRequestListResponse.observe(viewLifecycleOwner) { response ->
if (response?.data?.isNotEmpty() == true) {
val contactRequest = response.data[response.data.size - 1]
contactViewModel.contactRequestListResult.observe(viewLifecycleOwner) { result ->
result.onSuccess {
val items = result.getOrNull()?.toMutableList() ?: emptyList()
if (items.isNotEmpty()) {
friendAdapter.items[0].apply {
remark =
"${contactRequest.nickname}(${contactRequest.message})"
avatarUrl = contactRequest.avatarUrl
"${items[items.size - 1].nickname}(${items[items.size - 1].message})"
avatarUrl = items[items.size - 1].avatarUrl
}
friendAdapter.notifyItemChanged(0)
}
}
result.onFailure { }
}
contactViewModel.getContactRequestList(getUsername())
}
private fun loadData() {
loading = true
binding.tvLoading.visibility = View.VISIBLE
binding.recycleFriendList.visibility = View.INVISIBLE
lifecycleScope.launch(Dispatchers.IO) {
val contacts = contactViewModel.loadFriendListInDb()
val allItems = mutableListOf<Contact>().apply {
addAll(getDefaultItems())
addAll(contacts)
}
withContext(Dispatchers.Main) {
friendAdapter.updateData(allItems)
binding.tvLoading.visibility = View.GONE
binding.recycleFriendList.visibility = View.VISIBLE
loading = false
}
}
setupRecyclerView()
}

View File

@ -0,0 +1,74 @@
package com.kaixed.kchat.ui.fragment
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.Window
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import com.kaixed.kchat.databinding.DialogDeleteContactBinding
class DeleteContactDialogFragment : DialogFragment() {
interface OnContactDeletedListener {
fun onContactDeleted()
}
private lateinit var contactId: String
private var listener: OnContactDeletedListener? = null
companion object {
fun newInstance(contactId: String): DeleteContactDialogFragment {
val fragment = DeleteContactDialogFragment()
val args = Bundle()
args.putString("contactId", contactId)
fragment.arguments = args
return fragment
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnContactDeletedListener) {
listener = context
} else {
throw RuntimeException("$context must implement OnContactDeletedListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
contactId = arguments?.getString("contactId") ?: ""
val binding = DialogDeleteContactBinding.inflate(layoutInflater)
val dialog = Dialog(requireContext())
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(binding.root)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val layoutParams = WindowManager.LayoutParams().apply {
copyFrom(dialog.window?.attributes)
width = (requireContext().resources.displayMetrics.widthPixels * 0.8).toInt()
gravity = Gravity.CENTER
}
dialog.window?.attributes = layoutParams
binding.tvCancel.setOnClickListener {
dialog.dismiss()
}
binding.tvDelete.setOnClickListener {
listener?.onContactDeleted()
dialog.dismiss()
}
return dialog
}
}

View File

@ -0,0 +1,63 @@
package com.kaixed.kchat.ui.fragment
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Window
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import com.kaixed.kchat.databinding.DialogLoadingBinding
import com.kaixed.kchat.utils.DensityUtil.dpToPx
class LoadingDialogFragment : DialogFragment() {
// 设置加载文本
private var loadingText: String = ""
companion object {
// 通过这个方法设置加载文本,并创建实例
fun newInstance(text: String): LoadingDialogFragment {
val fragment = LoadingDialogFragment()
val bundle = Bundle()
bundle.putString("loading_text", text)
fragment.arguments = bundle
return fragment
}
}
// 创建对话框
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogLoadingBinding.inflate(layoutInflater)
val dialog = Dialog(requireContext())
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(binding.root)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.setCancelable(false) // 禁止点击外部区域关闭对话框
dialog.setCanceledOnTouchOutside(false) // 禁止点击外部区域消失
// 获取传入的 loading_text 参数
loadingText = arguments?.getString("loading_text", "") ?: "加载中..."
binding.tvLoading.text = loadingText
// 设置对话框的大小
val params = binding.root.layoutParams
params.height = dpToPx(150)
params.width = dpToPx(150)
binding.root.layoutParams = params
return dialog
}
// 显示对话框
fun showLoading(fragmentManager: FragmentManager) {
this.show(fragmentManager, "LoadingDialogFragment")
}
// 关闭对话框
fun dismissLoading() {
this.dismissAllowingStateLoss()
}
}

View File

@ -1,11 +1,10 @@
package com.kaixed.kchat.utils
import com.kaixed.kchat.utils.Utils.isChinese
import com.kaixed.kchat.utils.Utils.isEnglish
import net.sourceforge.pinyin4j.PinyinHelper
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType
/**
* @Author: kaixed
* @Date: 2024/11/2 14:30
@ -48,4 +47,12 @@ object Pinyin4jUtil {
private fun isEnglishOrChinese(str: String): Boolean {
return isChinese(str) || isEnglish(str)
}
private fun isChinese(str: String?): Boolean {
return str?.matches(".*[\\u4E00-\\u9FFF].*".toRegex()) ?: false
}
private fun isEnglish(str: String?): Boolean {
return str != null && str.matches("^[a-zA-Z]+$".toRegex())
}
}

View File

@ -1,16 +0,0 @@
package com.kaixed.kchat.utils
/**
* @Author: kaixed
* @Date: 2024/11/2 13:21
*/
object Utils {
fun isChinese(str: String?): Boolean {
return str?.matches(".*[\\u4E00-\\u9FFF].*".toRegex()) ?: false
}
fun isEnglish(str: String?): Boolean {
return str != null && str.matches("^[a-zA-Z]+$".toRegex())
}
}

View File

@ -6,13 +6,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kaixed.kchat.data.objectbox.ObjectBox.get
import com.kaixed.kchat.data.objectbox.entity.Contact
import com.kaixed.kchat.model.friend.AcceptContactRequest
import com.kaixed.kchat.model.friend.ContactRequestResponse
import com.kaixed.kchat.model.response.ApplyFriend
import com.kaixed.kchat.model.response.friend.DeleteContact
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.friend.FriendRequestItem
import com.kaixed.kchat.model.search.User
import com.kaixed.kchat.repository.ContactRepository
import com.kaixed.kchat.utils.Pinyin4jUtil
import io.objectbox.Box
import kotlinx.coroutines.launch
@ -21,86 +17,68 @@ class ContactViewModel : ViewModel() {
private val contactRepository = ContactRepository()
// 获取联系人请求列表
private val _contactRequestListResponse = MutableLiveData<ContactRequestResponse?>()
val contactRequestListResponse: LiveData<ContactRequestResponse?> = _contactRequestListResponse
private val _contactRequestListResult = MutableLiveData<Result<List<FriendRequestItem>?>>()
val contactRequestListResult: LiveData<Result<List<FriendRequestItem>?>> =
_contactRequestListResult
// 接受联系人请求
private val _acceptContactRequestResponse = MutableLiveData<AcceptContactRequest?>()
val acceptContactRequestResponse: LiveData<AcceptContactRequest?> =
_acceptContactRequestResponse
private val _acceptContactRequestResult = MutableLiveData<Result<Contact?>>()
val acceptContactRequestResult: LiveData<Result<Contact?>> =
_acceptContactRequestResult
// 添加联系人
private val _addContactResponse = MutableLiveData<ApplyFriend?>()
val addContactResponse: LiveData<ApplyFriend?> = _addContactResponse
private val _addContactResult = MutableLiveData<Result<String>>()
val addContactResult: LiveData<Result<String>> = _addContactResult
// 搜索联系人
private val _searchContactResponse = MutableLiveData<SearchFriends?>()
val searchContactResponse: LiveData<SearchFriends?> = _searchContactResponse
private val _searchContactResult = MutableLiveData<Result<User?>>()
val searchContactResult: LiveData<Result<User?>> = _searchContactResult
// 获取联系人列表
private val _contactListResponse = MutableLiveData<List<Contact>?>()
val contactListResponse: LiveData<List<Contact>?> = _contactListResponse
private val _contactListResult = MutableLiveData<Result<List<Contact>?>>()
val contactListResult: LiveData<Result<List<Contact>?>> = _contactListResult
// 删除联系人
private val _deleteContact = MutableLiveData<DeleteContact?>()
val deleteContact: LiveData<DeleteContact?> = _deleteContact
private val _deleteContactResult = MutableLiveData<Result<String?>>()
val deleteContactResult: LiveData<Result<String?>> = _deleteContactResult
// 删除联系人
fun deleteContact(username: String, contactId: String) {
viewModelScope.launch {
val response = contactRepository.deleteContact(username, contactId)
if (response.isSuccessful) {
_deleteContact.value = response.body()
} else {
_deleteContact.value = null
}
val result = contactRepository.deleteContact(username, contactId)
_deleteContactResult.postValue(result)
}
}
// 获取联系人请求列表
fun getContactRequestList(username: String) {
viewModelScope.launch {
val response = contactRepository.getContactRequestList(username)
if (response.isSuccessful) {
_contactRequestListResponse.value = response.body()
} else {
_contactRequestListResponse.value = null
}
val result = contactRepository.getContactRequestList(username)
_contactRequestListResult.postValue(result)
}
}
// 接受联系人请求
fun acceptContactRequest(username: String, contactId: String, remark: String) {
viewModelScope.launch {
val response = contactRepository.acceptContactRequest(username, contactId, remark)
if (response.isSuccessful) {
_acceptContactRequestResponse.value = response.body()
} else {
_acceptContactRequestResponse.value = null
}
val result = contactRepository.acceptContactRequest(username, contactId, remark)
_acceptContactRequestResult.postValue(result)
}
}
// 添加联系人
fun addContact(contactId: String, message: String) {
viewModelScope.launch {
val response = contactRepository.addContact(contactId, message)
if (response.isSuccessful) {
_addContactResponse.value = response.body()
} else {
_addContactResponse.value = null
}
val result = contactRepository.addContact(contactId, message)
_addContactResult.postValue(result)
}
}
// 搜索联系人
fun searchContact(username: String) {
viewModelScope.launch {
val response = contactRepository.searchContact(username)
if (response.isSuccessful) {
_searchContactResponse.value = response.body()
} else {
_searchContactResponse.value = null
}
val result = contactRepository.searchContact(username)
_searchContactResult.postValue(result)
}
}
@ -140,56 +118,8 @@ class ContactViewModel : ViewModel() {
fun loadFriendList(username: String) {
viewModelScope.launch {
val response = contactRepository.getContactList(username)
if (response.isSuccessful) {
val friendListResponse = response.body()?.data
val uiFriendList = friendListResponse?.map { networkItem ->
mapToFriendItem(networkItem) // 数据转换
}?.sortedWith { f1, f2 ->
val str1 = f1.remarkquanpin ?: f1.quanpin ?: f1.nickname
val str2 = f2.remarkquanpin ?: f2.quanpin ?: f2.nickname
val type1 = when {
str1.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str1.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
val type2 = when {
str2.matches(Regex("[a-zA-Z]+")) -> 1 // 字母
str2.matches(Regex("[0-9]+")) -> 2 // 数字
else -> 3 // 特殊字符
}
// 比较类型,如果相同再按字母升序排序
if (type1 != type2) {
type1 - type2 // 按类型排序
} else {
str1.compareTo(str2) // 如果类型相同,按字母顺序排序
}
}
// 设置是否显示分组头
uiFriendList?.forEachIndexed { index, item ->
item.showHeader =
(index == 0 || item.quanpin?.get(index) != uiFriendList[index - 1].quanpin?.get(
index - 1
))
}
_contactListResponse.value = uiFriendList
} else {
_contactListResponse.value = null
val result = contactRepository.getContactList(username)
_contactListResult.postValue(result)
}
}
}
private fun mapToFriendItem(response: Contact): Contact {
val pinyin = Pinyin4jUtil.toPinyin(response.nickname)
response.remark?.let {
response.remarkquanpin = Pinyin4jUtil.toPinyin(it)
}
response.quanpin = pinyin
return response
}
}

View File

@ -1,143 +1,98 @@
package com.kaixed.kchat.viewmodel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kaixed.kchat.data.objectbox.entity.UserInfo
import com.kaixed.kchat.model.request.RegisterRequest
import com.kaixed.kchat.model.request.UserRequest
import com.kaixed.kchat.model.response.friend.SearchFriends
import com.kaixed.kchat.model.response.login.Login
import com.kaixed.kchat.model.response.register.Register
import com.kaixed.kchat.model.response.search.UserList
import com.kaixed.kchat.repository.UserRepository
import com.kaixed.kchat.model.response.search.User
import com.kaixed.kchat.model.search.SearchUser
import com.kaixed.kchat.repository.UserAuthRepository
import com.kaixed.kchat.repository.UserProfileRepository
import com.kaixed.kchat.repository.UserSearchRepository
import kotlinx.coroutines.launch
import okhttp3.MultipartBody
class UserViewModel : ViewModel() {
private val userRepo = UserRepository()
private val userAuthRepo = UserAuthRepository()
// 注册返回
private val _registerResponse = MutableLiveData<Register?>()
val registerResponse: LiveData<Register?> = _registerResponse
private val userProfileRepo = UserProfileRepository()
// 获取用户列表返回
private val _userListResponse = MutableLiveData<UserList?>()
val userListResponse: LiveData<UserList?> = _userListResponse
private val userSearchRepo = UserSearchRepository()
// 获取用户信息返回
private val _userInfoResponse = MutableLiveData<SearchFriends?>()
val userInfoResponse: LiveData<SearchFriends?> = _userInfoResponse
private val _loginResult = MutableLiveData<Result<UserInfo>>()
val loginResult: LiveData<Result<UserInfo>> = _loginResult
// 修改昵称返回
private val _changeNicknameResponse = MutableLiveData<Boolean>()
val changeNicknameResponse: LiveData<Boolean> = _changeNicknameResponse
fun loginByUsername(username: String, password: String) {
viewModelScope.launch {
val result = userAuthRepo.loginByUsername(username, password)
_loginResult.postValue(result)
}
}
// 上传头像返回
private val _uploadAvatarResponse = MutableLiveData<String?>()
val uploadAvatarResponse: LiveData<String?> = _uploadAvatarResponse
fun loginByTelephone(username: String, password: String) {
viewModelScope.launch {
val result = userAuthRepo.loginByTelephone(username, password)
_loginResult.postValue(result)
}
}
// 登录返回
private val _loginResponse = MutableLiveData<Login?>()
val loginResponse: LiveData<Login?> = _loginResponse
private val _registerResult = MutableLiveData<Result<Register>>()
val registerResult: LiveData<Result<Register>> = _registerResult
// 注册请求
fun register(registerRequest: RegisterRequest) {
viewModelScope.launch {
try {
val response = userRepo.register(registerRequest)
if (response.isSuccessful) {
_registerResponse.value = response.body()
} else {
_registerResponse.value = null
}
} catch (e: Exception) {
Log.e("UserViewModel", "Register failed: ${e.message}")
_registerResponse.value = null
}
val result = userAuthRepo.register(registerRequest)
_registerResult.postValue(result)
}
}
// 登录方法
fun login(username: String?, password: String, loginByUsername: Boolean) {
viewModelScope.launch {
try {
val response = userRepo.login(username, password, loginByUsername)
if (response.isSuccessful) {
_loginResponse.value = response.body()
} else {
_loginResponse.value = null // 登录失败时
}
} catch (e: Exception) {
Log.e("UserViewModel", "Login failed: ${e.message}")
_loginResponse.value = null
}
}
}
private val _userListResult = MutableLiveData<Result<List<User>>>()
val userListResult: LiveData<Result<List<User>>> = _userListResult
// 获取用户列表
fun getUserList(username: String) {
viewModelScope.launch {
try {
val response = userRepo.getUserList(username)
if (response.isSuccessful) {
_userListResponse.value = response.body()
} else {
_userListResponse.value = null
}
} catch (e: Exception) {
Log.e("UserViewModel", "Get User List failed: ${e.message}")
_userListResponse.value = null
}
val result = userSearchRepo.getUserList(username)
_userListResult.postValue(result)
}
}
private val _userInfoResult = MutableLiveData<Result<SearchUser>>()
val userInfoResult: LiveData<Result<SearchUser>> = _userInfoResult
// 获取用户信息
fun getUserInfo(username: String) {
viewModelScope.launch {
try {
val response = userRepo.getUserInfo(username)
if (response.isSuccessful) {
_userInfoResponse.value = response.body()
} else {
_userInfoResponse.value = null
}
} catch (e: Exception) {
Log.e("UserViewModel", "Get User failed: ${e.message}")
_userInfoResponse.value = null
}
val result = userProfileRepo.getUserInfo(username)
_userInfoResult.postValue(result)
}
}
private val _changeNicknameResult = MutableLiveData<Result<Boolean>>()
val changeNicknameResult: LiveData<Result<Boolean>> = _changeNicknameResult
// 修改昵称
fun changeNickname(userRequest: UserRequest) {
viewModelScope.launch {
try {
val response = userRepo.changeNickname(userRequest)
_changeNicknameResponse.value = response.isSuccessful
} catch (e: Exception) {
Log.e("UserViewModel", "Change Nickname failed: ${e.message}")
_changeNicknameResponse.value = false
}
val result = userProfileRepo.changeNickname(userRequest)
_changeNicknameResult.postValue(result)
}
}
private val _uploadAvatarResult = MutableLiveData<Result<String>>()
val uploadAvatarResult: LiveData<Result<String>> = _uploadAvatarResult
// 上传头像
fun uploadAvatar(file: MultipartBody.Part, username: String) {
viewModelScope.launch {
try {
val response = userRepo.uploadAvatar(file, username)
if (response.isSuccessful) {
_uploadAvatarResponse.value = response.body()
} else {
_uploadAvatarResponse.value = null
}
} catch (e: Exception) {
Log.e("UserViewModel", "Upload Avatar failed: ${e.message}")
_uploadAvatarResponse.value = null
}
val result = userProfileRepo.uploadAvatar(file, username)
_uploadAvatarResult.postValue(result)
}
}
}

View File

@ -55,7 +55,7 @@
android:layout_height="wrap_content"
android:text="kid: kaixed"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/tv_contact_name"
app:layout_constraintStart_toStartOf="@id/tv_remark"
app:layout_constraintTop_toBottomOf="@id/tv_contact_name" />
<TextView
@ -64,7 +64,7 @@
android:layout_height="wrap_content"
android:text="平安顺遂,喜乐无忧"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@id/tv_contact_name"
app:layout_constraintStart_toStartOf="@id/tv_remark"
app:layout_constraintTop_toBottomOf="@id/tv_contact_id" />
<androidx.constraintlayout.widget.Barrier

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.235.209</domain>
<domain includeSubdomains="true">192.168.156.209</domain>
</domain-config>
</network-security-config>