diff --git a/app/objectbox-models/default.json b/app/objectbox-models/default.json index 1bfa6eb..69401e1 100644 --- a/app/objectbox-models/default.json +++ b/app/objectbox-models/default.json @@ -154,7 +154,7 @@ }, { "id": "5:2885532406154205395", - "lastPropertyId": "7:7996607163318458427", + "lastPropertyId": "9:1671273767790122969", "name": "UserInfo", "properties": [ { @@ -194,6 +194,16 @@ "id": "7:7996607163318458427", "name": "status", "type": 9 + }, + { + "id": "8:6299664727899448104", + "name": "accessToken", + "type": 9 + }, + { + "id": "9:1671273767790122969", + "name": "refreshToken", + "type": 9 } ], "relations": [] diff --git a/app/objectbox-models/default.json.bak b/app/objectbox-models/default.json.bak index c5f404f..1bfa6eb 100644 --- a/app/objectbox-models/default.json.bak +++ b/app/objectbox-models/default.json.bak @@ -74,7 +74,7 @@ }, { "id": "4:6179749773128044271", - "lastPropertyId": "14:5371512009949707960", + "lastPropertyId": "15:191614052410347083", "name": "Contact", "properties": [ { @@ -145,8 +145,8 @@ "type": 9 }, { - "id": "14:5371512009949707960", - "name": "disturb", + "id": "15:191614052410347083", + "name": "doNotDisturb", "type": 1 } ], @@ -298,7 +298,8 @@ 2020630799900991467, 385998119105891942, 8166842332862045141, - 605708604168234493 + 605708604168234493, + 5371512009949707960 ], "retiredRelationUids": [], "version": 1 diff --git a/app/src/main/assets/loading2.json b/app/src/main/assets/loading2.json new file mode 100644 index 0000000..bd86df4 --- /dev/null +++ b/app/src/main/assets/loading2.json @@ -0,0 +1 @@ +{"ddd":0,"fr":100,"h":200,"ip":0,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"loader_21","ln":"WlKberZN","sr":1,"ks":{"a":{"ix":2,"a":0,"k":[0,0]},"p":{"ix":2,"a":0,"k":[0,0]},"s":{"ix":2,"a":0,"k":[100,100]},"r":{"ix":2,"a":0,"k":0},"sk":{"ix":2,"a":0,"k":0},"sa":{"ix":2,"a":0,"k":0},"o":{"ix":2,"a":0,"k":100}},"ao":0,"ip":0,"op":101,"st":0,"bm":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","nm":"Shape 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,-19.75],[0,20]],"i":[[0,0],[0,0]],"o":[[0,0],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":0,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[0.45098039215686275,0.4235294117647059,0.9294117647058824],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":16,"ix":2},"lc":2,"lj":2,"ml":4},{"ty":"tr","p":{"a":0,"k":[40,40.125],"ix":2},"a":{"a":0,"k":[0,0.125],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":43,"s":[90],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[90],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":103,"s":[180],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Shape 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,-19.75],[0,20]],"i":[[0,0],[0,0]],"o":[[0,0],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":0,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[0.9607843137254902,0.3607843137254902,0.47843137254901963],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":16,"ix":2},"lc":2,"lj":2,"ml":4},{"ty":"tr","p":{"a":0,"k":[40,40.125],"ix":2},"a":{"a":0,"k":[0,0.125],"ix":2},"s":{"a":1,"k":[{"t":0,"s":[100,100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[94,94],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[94,94],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[100,100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[90],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":43,"s":[180],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[180],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":103,"s":[270],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[100.0000038146973,99.99999618530273],"ix":2},"a":{"a":0,"k":[39.99999904632568,40.12500286102295],"ix":2},"s":{"a":0,"k":[150,150],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}]}],"markers":[],"meta":{"g":"jsDesign"},"nm":"Comp 1","op":100,"v":"5.7.5","w":200} \ No newline at end of file diff --git a/app/src/main/kotlin/com/kaixed/kchat/data/local/entity/UserInfo.kt b/app/src/main/kotlin/com/kaixed/kchat/data/local/entity/UserInfo.kt index aad45e5..8e42613 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/data/local/entity/UserInfo.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/data/local/entity/UserInfo.kt @@ -21,5 +21,7 @@ data class UserInfo( var avatarUrl: String, var signature: String, var telephone: String, - var status: String? = null + var status: String? = null, + var accessToken: String? = null, + var refreshToken: String? = null ) diff --git a/app/src/main/kotlin/com/kaixed/kchat/data/repository/UserAuthRepository.kt b/app/src/main/kotlin/com/kaixed/kchat/data/repository/UserAuthRepository.kt index ac58d70..cf7e41e 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/data/repository/UserAuthRepository.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/data/repository/UserAuthRepository.kt @@ -17,7 +17,7 @@ import io.objectbox.Box */ class UserAuthRepository { - private val userApiService = RetrofitClient.userApiService + private val authApiService = RetrofitClient.authApiService private val userInfoBox: Box by lazy { getBoxStore().boxFor(UserInfo::class.java) } @@ -26,7 +26,7 @@ class UserAuthRepository { // 用户注册 suspend fun register(registerRequest: RegisterRequest): Result { return apiCall( - apiCall = { userApiService.register(registerRequest) }, + apiCall = { authApiService.register(registerRequest) }, errorMessage = "注册成功,但未返回用户数据" ).onSuccess { register -> register?.let { @@ -38,7 +38,7 @@ class UserAuthRepository { // 登录方法 suspend fun loginByUsername(username: String, password: String): Result { return apiCall( - apiCall = { userApiService.loginByUsername(username, password) }, + apiCall = { authApiService.loginByUsername(username, password) }, errorMessage = "登录成功,但未返回用户数据" ).onSuccess { userInfo -> userInfo?.let { @@ -50,7 +50,7 @@ class UserAuthRepository { suspend fun loginByTelephone(telephone: String, password: String): Result { return apiCall( - apiCall = { userApiService.loginByTelephone(telephone, password) }, + apiCall = { authApiService.loginByTelephone(telephone, password) }, errorMessage = "登录成功,但未返回用户数据" ).onSuccess { userInfo -> userInfo?.let { diff --git a/app/src/main/kotlin/com/kaixed/kchat/manager/AppManager.kt b/app/src/main/kotlin/com/kaixed/kchat/manager/AppManager.kt new file mode 100644 index 0000000..4799de7 --- /dev/null +++ b/app/src/main/kotlin/com/kaixed/kchat/manager/AppManager.kt @@ -0,0 +1,96 @@ +package com.kaixed.kchat.manager + +/** + * @Author: kaixed + * @Date: 2025/1/15 21:29 + */ +import android.app.Activity +import android.content.Context +import java.util.Stack + +class AppManager private constructor() { + + private val activityStack: Stack = Stack() + + companion object { + val instance: AppManager by lazy { AppManager() } + } + + /** + * 添加 Activity 到堆栈 + */ + fun addActivity(activity: Activity) { + activityStack.add(activity) + } + + /** + * 获取当前 Activity(堆栈中最后一个压入的) + */ + fun currentActivity(): Activity? { + return if (activityStack.isNotEmpty()) { + activityStack.lastElement() + } else { + null + } + } + + /** + * 结束当前 Activity(堆栈中最后一个压入的) + */ + fun finishActivity() { + currentActivity()?.let { finishActivity(it) } + } + + /** + * 结束指定的 Activity + */ + fun finishActivity(activity: Activity) { + activityStack.remove(activity) + activity.finish() + } + + /** + * 结束指定类名的 Activity + */ + fun finishActivity(cls: Class<*>) { + val iterator = activityStack.iterator() + while (iterator.hasNext()) { + val activity = iterator.next() + if (activity.javaClass == cls) { + iterator.remove() + activity.finish() + } + } + } + + /** + * 结束所有 Activity + */ + fun finishAllActivities() { + while (activityStack.isNotEmpty()) { + val activity = activityStack.pop() + activity.finish() + } + } + + /** + * 结束所有 Activity + */ + fun finishAllActivities(activity: Activity) { + activity.finishAffinity() + } + + /** + * 退出应用程序 + */ + fun appExit(context: Context) { + try { + finishAllActivities() + val activityMgr = + context.getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager + activityMgr.restartPackage(context.packageName) // 注意:此方法已被废弃,实际应用中可能需要调整退出逻辑 + } catch (e: Exception) { + e.printStackTrace() + } + } +} diff --git a/app/src/main/kotlin/com/kaixed/kchat/network/NetworkInterface.kt b/app/src/main/kotlin/com/kaixed/kchat/network/NetworkInterface.kt index 4cbdf15..172bbb5 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/network/NetworkInterface.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/network/NetworkInterface.kt @@ -7,9 +7,10 @@ package com.kaixed.kchat.network object NetworkInterface { // private const val URL = "app.kaixed.com/kchat" -// private const val URL = "192.168.45.209:6196/kchat" - private const val URL = "49.233.105.103:6000" - const val SERVER_URL = "https://$URL" + private const val URL = "192.168.31.18:6196" +// private const val URL = "49.233.105.103:6000" +// const val SERVER_URL = "https://$URL" + const val SERVER_URL = "http://$URL" const val WEBSOCKET_SERVER_URL = "wss://$URL" const val WEBSOCKET = "/websocket/single/" const val USER_INFO = "/users/info/" diff --git a/app/src/main/kotlin/com/kaixed/kchat/network/RetrofitClient.kt b/app/src/main/kotlin/com/kaixed/kchat/network/RetrofitClient.kt index d47d9d9..7c6412b 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/network/RetrofitClient.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/network/RetrofitClient.kt @@ -1,5 +1,7 @@ package com.kaixed.kchat.network +import com.kaixed.kchat.network.interceptor.TokenRefreshInterceptor +import com.kaixed.kchat.network.service.AuthApiService import com.kaixed.kchat.network.service.ContactService import com.kaixed.kchat.network.service.FileApiService import com.kaixed.kchat.network.service.UserApiService @@ -7,6 +9,7 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit /** * @Author: kaixed @@ -14,28 +17,50 @@ 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.45.209:6196/" - private const val BASE_URL = "http://49.233.105.103:6000/" + private const val BASE_URL = "${NetworkInterface.SERVER_URL}/" + // 添加日志拦截器 private val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } - private val client = OkHttpClient.Builder() + // 创建一个不包含 Token 拦截器的 OkHttpClient,专门用于授权 API + private val authClient = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) -// .addInterceptor(SignInterceptor()) // 添加签名拦截器 + .pingInterval(15, TimeUnit.SECONDS) .build() + // 创建单独的 Retrofit 实例(专门用于授权 API) + private val authRetrofit: Retrofit by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(authClient) // 使用不包含 Token 拦截器的 OkHttpClient + .build() + } + // 授权 API 服务实例,使用不包含 Token 拦截器的 Retrofit 实例 + val authApiService: AuthApiService by lazy { + authRetrofit.create(AuthApiService::class.java) + } + + // 创建一个 OkHttpClient,包含 Token 拦截器 + private val client = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .pingInterval(15, TimeUnit.SECONDS) + .addInterceptor(TokenRefreshInterceptor()) // Token 拦截器 + .build() + + // 创建 Retrofit 实例(用于普通 API 调用) private val retrofit: Retrofit by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) - .client(client) + .client(client) // 使用包含 Token 拦截器的 OkHttpClient .build() } + // API 服务实例 val userApiService: UserApiService by lazy { retrofit.create(UserApiService::class.java) } diff --git a/app/src/main/kotlin/com/kaixed/kchat/network/interceptor/TokenRefreshInterceptor.kt b/app/src/main/kotlin/com/kaixed/kchat/network/interceptor/TokenRefreshInterceptor.kt new file mode 100644 index 0000000..142706d --- /dev/null +++ b/app/src/main/kotlin/com/kaixed/kchat/network/interceptor/TokenRefreshInterceptor.kt @@ -0,0 +1,127 @@ +package com.kaixed.kchat.network.interceptor + +import android.util.Base64 +import android.util.Log +import com.kaixed.kchat.network.ApiCall +import com.kaixed.kchat.network.RetrofitClient +import com.kaixed.kchat.utils.CacheUtils +import com.kaixed.kchat.utils.ConstantsUtils.getUsername +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import okhttp3.Interceptor +import okhttp3.Response +import org.json.JSONObject +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +class TokenRefreshInterceptor() : Interceptor { + + private val lock = ReentrantLock() + + private val authApiService by lazy { RetrofitClient.authApiService } + + override fun intercept(chain: Interceptor.Chain): Response { + // 获取当前的 accessToken + var accessToken = CacheUtils.getAccessToken() + var refreshToken = CacheUtils.getRefreshToken() + val expiration = parseTokenManually(accessToken) + val refreshTokenExpiration = parseTokenManually(refreshToken) + val now = System.currentTimeMillis() / 1000 + Log.d("haha", now.toString()) + + // 如果 Token 距离过期时间小于 5 分钟,刷新 Token + if (expiration - now < 300) { + lock.withLock { + // Double-check 防止并发刷新 + val latestAccessToken = CacheUtils.getAccessToken() + if (latestAccessToken == accessToken) { + // 调用刷新 Token 方法 + runBlocking { + refreshAccessToken() // 刷新 Refresh Token + } + } else { + accessToken = latestAccessToken + } + } + } + + // 判断 Refresh Token 是否即将过期(提前一天刷新) + if (refreshTokenExpiration - now < 24 * 60 * 60) { // 剩余小于24小时 + lock.withLock { + val latestRefreshToken = CacheUtils.getRefreshToken() + if (latestRefreshToken == refreshToken) { + runBlocking { + refreshRefreshToken() // 刷新 Refresh Token + } + } + } + } + + // 将最新的 Token 添加到请求头 + val newRequest = chain.request().newBuilder() + .addHeader("Authorization", "Bearer $accessToken") + .build() + + return chain.proceed(newRequest) + } + + private suspend fun refreshAccessToken() { + return withContext(Dispatchers.IO) { + try { + ApiCall.apiCall( + apiCall = { authApiService.auth("Bearer ${CacheUtils.getRefreshToken()}", getUsername()) }, + errorMessage = "刷新Token失败" + ).onSuccess { + it?.let { + CacheUtils.setAccessToken(it) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + private suspend fun refreshRefreshToken() { + return withContext(Dispatchers.IO) { + try { + ApiCall.apiCall( + apiCall = { + authApiService.refresh( + CacheUtils.getRefreshToken(), + getUsername() + ) + }, + errorMessage = "刷新Token失败" + ).onSuccess { + it?.let { + CacheUtils.setRefreshToken(it) + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + private fun parseTokenManually(token: String): Long { + var expiration = 0L + try { + // JWT 的有效载荷部分是第二部分 + val payload = token.split(".")[1] + + // 解码Base64编码的字符串 + val decodedBytes = Base64.decode(payload, Base64.URL_SAFE or Base64.NO_WRAP) + val decodedString = String(decodedBytes) + + // 将解码后的字符串转为JSONObject + val jsonObject = JSONObject(decodedString) + + expiration = jsonObject.optLong("exp") // 获取过期时间 + } catch (e: Exception) { + e.printStackTrace() + } + return expiration + } +} diff --git a/app/src/main/kotlin/com/kaixed/kchat/network/service/AuthApiService.kt b/app/src/main/kotlin/com/kaixed/kchat/network/service/AuthApiService.kt new file mode 100644 index 0000000..c63d7b4 --- /dev/null +++ b/app/src/main/kotlin/com/kaixed/kchat/network/service/AuthApiService.kt @@ -0,0 +1,53 @@ +package com.kaixed.kchat.network.service + +import com.kaixed.kchat.data.local.entity.UserInfo +import com.kaixed.kchat.data.model.request.RegisterRequest +import com.kaixed.kchat.data.model.response.register.Register +import com.kaixed.kchat.network.ApiResponse +import retrofit2.http.Body +import retrofit2.http.Field +import retrofit2.http.FormUrlEncoded +import retrofit2.http.Header +import retrofit2.http.POST +import retrofit2.http.Query + +/** + * @Author: kaixed + * @Date: 2025/1/22 18:35 + */ +interface AuthApiService { + + @POST("users/access-token") + suspend fun auth( + @Header("Authorization") token: String, + @Query("username") username: String + ): ApiResponse + + @POST("users/refresh-token") + suspend fun refresh( + @Header("Authorization") token: String, + @Query("username") username: String + ): ApiResponse + + // 登录接口(根据用户名登录) + @FormUrlEncoded + @POST("users/login/username") + suspend fun loginByUsername( + @Field("username") username: String, + @Field("password") password: String + ): ApiResponse + + // 登录接口(根据电话登录) + @FormUrlEncoded + @POST("users/login/telephone") + suspend fun loginByTelephone( + @Field("telephone") telephone: String, + @Field("password") password: String + ): ApiResponse + + // 注册接口 + @POST("users/register") + suspend fun register( + @Body registerRequest: RegisterRequest + ): ApiResponse +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/kaixed/kchat/network/service/UserApiService.kt b/app/src/main/kotlin/com/kaixed/kchat/network/service/UserApiService.kt index 208dfbc..1907e7b 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/network/service/UserApiService.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/network/service/UserApiService.kt @@ -24,28 +24,6 @@ import retrofit2.http.Path */ interface UserApiService { - // 注册接口 - @POST("users/register") - suspend fun register( - @Body registerRequest: RegisterRequest - ): ApiResponse - - // 登录接口(根据用户名登录) - @FormUrlEncoded - @POST("users/login/username") - suspend fun loginByUsername( - @Field("username") username: String, - @Field("password") password: String - ): ApiResponse - - // 登录接口(根据电话登录) - @FormUrlEncoded - @POST("users/login/telephone") - suspend fun loginByTelephone( - @Field("telephone") telephone: String, - @Field("password") password: String - ): ApiResponse - // 获取用户列表 @GET("userList/{username}") suspend fun getUserListByNickname( diff --git a/app/src/main/kotlin/com/kaixed/kchat/ui/activity/LoginActivity.kt b/app/src/main/kotlin/com/kaixed/kchat/ui/activity/LoginActivity.kt index 4fd5008..10a221a 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/ui/activity/LoginActivity.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/ui/activity/LoginActivity.kt @@ -15,9 +15,10 @@ import com.kaixed.kchat.R import com.kaixed.kchat.data.LocalDatabase import com.kaixed.kchat.databinding.ActivityLoginBinding import com.kaixed.kchat.ui.base.BaseActivity +import com.kaixed.kchat.utils.Constants.ACCESS_TOKEN import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO -import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA +import com.kaixed.kchat.utils.Constants.REFRESH_TOKEN import com.kaixed.kchat.utils.DrawableUtil.createDrawable import com.kaixed.kchat.utils.ScreenUtils.dp2px import com.kaixed.kchat.viewmodel.UserViewModel @@ -80,6 +81,8 @@ class LoginActivity : BaseActivity() { loginResult.onSuccess { it?.let { LocalDatabase.saveUserInfo(it) + MMKV.defaultMMKV().putString(ACCESS_TOKEN, it.accessToken) + MMKV.defaultMMKV().putString(REFRESH_TOKEN, it.refreshToken) } navigateToMain() } diff --git a/app/src/main/kotlin/com/kaixed/kchat/ui/activity/MainActivity.kt b/app/src/main/kotlin/com/kaixed/kchat/ui/activity/MainActivity.kt index 994182c..42e20af 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/ui/activity/MainActivity.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/ui/activity/MainActivity.kt @@ -90,13 +90,6 @@ class MainActivity : BaseActivity() { initViewPager() setListener() - preLoad() - } - - private fun preLoad() { - Glide.with(this) - .load(getAvatarUrl()) - .preload() } // 待优化成自定义界面,目前使用的华为sdk的默认界面 @@ -182,7 +175,6 @@ class MainActivity : BaseActivity() { lifecycleScope.launch(Dispatchers.Main) { delay(200) loadingDialogFragment.showLoading(supportFragmentManager) - contactViewModel.loadFriendList(getUsername()) } } diff --git a/app/src/main/kotlin/com/kaixed/kchat/ui/base/BaseActivity.kt b/app/src/main/kotlin/com/kaixed/kchat/ui/base/BaseActivity.kt index eed946d..20e0a99 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/ui/base/BaseActivity.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/ui/base/BaseActivity.kt @@ -1,10 +1,10 @@ package com.kaixed.kchat.ui.base import android.os.Bundle -import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.viewbinding.ViewBinding +import com.kaixed.kchat.manager.AppManager /** * @Author: kaixed @@ -21,6 +21,12 @@ abstract class BaseActivity : AppCompatActivity() { binding = inflateBinding() setContentView(binding.root) initData() + AppManager.instance.addActivity(this) + } + + override fun onDestroy() { + super.onDestroy() + AppManager.instance.finishActivity(this) } abstract fun initData() diff --git a/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/ContactFragment.kt b/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/ContactFragment.kt index 0c0269b..528a8a4 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/ContactFragment.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/ContactFragment.kt @@ -56,6 +56,7 @@ class ContactFragment : BaseFragment() { context = requireContext() loadData() + setObservation() loadFriendRequest() @@ -63,17 +64,7 @@ class ContactFragment : BaseFragment() { LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(mReceiver, filter) } - override fun onResume() { - super.onResume() - loadFriendRequest() - } - - override fun onDestroy() { - super.onDestroy() - LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver); - } - - private fun loadFriendRequest() { + private fun setObservation() { contactViewModel.contactRequestListResult.observe(viewLifecycleOwner) { result -> result.onSuccess { val items = result.getOrNull()?.toMutableList() ?: emptyList() @@ -88,12 +79,25 @@ class ContactFragment : BaseFragment() { } result.onFailure { } } + } + + override fun onResume() { + super.onResume() + loadFriendRequest() + } + + override fun onDestroy() { + super.onDestroy() + LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver); + } + + private fun loadFriendRequest() { contactViewModel.getContactRequestList(getUsername()) } private fun loadData() { loading = true - binding.tvLoading.visibility = View.VISIBLE + binding.includeLoading.main.visibility = View.VISIBLE binding.recycleFriendList.visibility = View.INVISIBLE val contacts = contactViewModel.loadFriendListInDb() @@ -102,7 +106,7 @@ class ContactFragment : BaseFragment() { addAll(contacts) } friendAdapter.updateData(allItems) - binding.tvLoading.visibility = View.GONE + binding.includeLoading.main.visibility = View.GONE binding.recycleFriendList.visibility = View.VISIBLE loading = false setupRecyclerView() diff --git a/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/DiscoveryFragment.kt b/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/DiscoveryFragment.kt index df43a6d..8f5ac72 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/DiscoveryFragment.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/ui/fragment/DiscoveryFragment.kt @@ -28,7 +28,6 @@ class DiscoveryFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setListener() } diff --git a/app/src/main/kotlin/com/kaixed/kchat/utils/CacheUtils.kt b/app/src/main/kotlin/com/kaixed/kchat/utils/CacheUtils.kt new file mode 100644 index 0000000..d1ccc9b --- /dev/null +++ b/app/src/main/kotlin/com/kaixed/kchat/utils/CacheUtils.kt @@ -0,0 +1,27 @@ +package com.kaixed.kchat.utils + +import com.tencent.mmkv.MMKV + +/** + * @Author: kaixed + * @Date: 2025/1/22 18:06 + */ +object CacheUtils { + fun getAccessToken(): String { + val accessToken = MMKV.defaultMMKV().getString(Constants.ACCESS_TOKEN, "") + return accessToken ?: "" + } + + fun getRefreshToken(): String { + val refreshToken = MMKV.defaultMMKV().getString(Constants.REFRESH_TOKEN, "") + return refreshToken ?: "" + } + + fun setAccessToken(accessToken: String) { + MMKV.defaultMMKV().putString(Constants.ACCESS_TOKEN, accessToken) + } + + fun setRefreshToken(refreshToken: String) { + MMKV.defaultMMKV().putString(Constants.REFRESH_TOKEN, refreshToken) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/kaixed/kchat/utils/Constants.kt b/app/src/main/kotlin/com/kaixed/kchat/utils/Constants.kt index cfd7e79..a2368a5 100644 --- a/app/src/main/kotlin/com/kaixed/kchat/utils/Constants.kt +++ b/app/src/main/kotlin/com/kaixed/kchat/utils/Constants.kt @@ -22,6 +22,9 @@ object Constants { const val SCREEN_HEIGHT = "screenHeight" const val SCREEN_WIDTH = "screenWidth" + const val ACCESS_TOKEN = "accessToken" + const val REFRESH_TOKEN = "refreshToken" + const val KEYBOARD_HEIGHT_RATIO = 0.15F const val KEYBOARD_DEFAULT_HEIGHT = 200 const val STATUS_BAR_DEFAULT_HEIGHT: Int = 50 diff --git a/app/src/main/res/layout/fragment_contact.xml b/app/src/main/res/layout/fragment_contact.xml index 676e0db..126aa7a 100644 --- a/app/src/main/res/layout/fragment_contact.xml +++ b/app/src/main/res/layout/fragment_contact.xml @@ -26,13 +26,8 @@ android:background="@color/white" android:overScrollMode="never" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_loading.xml b/app/src/main/res/layout/layout_loading.xml new file mode 100644 index 0000000..4341596 --- /dev/null +++ b/app/src/main/res/layout/layout_loading.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index af7113b..11c50c6 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -2,5 +2,6 @@ 49.233.105.103 + 192.168.31.18 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c809b3b..79054c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,10 +32,12 @@ window = "1.3.0" kotlin = "1.9.23" coreKtx = "1.13.1" objectbox = "4.0.2" +workRuntimeKtx = "2.10.0" [libraries] agcp = { module = "com.huawei.agconnect:agcp", version.ref = "agcp" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } compress = { module = "io.github.lucksiege:compress", version.ref = "pictureselector" } eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" }