From 04b138d3836e03c9adbcbd367fd71d92905c5206 Mon Sep 17 00:00:00 2001 From: cloudroam <cloudroam> Date: 星期四, 17 四月 2025 13:17:45 +0800 Subject: [PATCH] add: 重复登录处理 --- app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt | 43 ++++ app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt | 52 +++++ app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt | 32 +++ app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt | 43 ++++ app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt | 45 ++++ app/src/main/java/com/example/firstapp/database/service/ApiService.kt | 47 +++- app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt | 85 +++++++++ app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt | 52 +++++ app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt | 138 +++++++++++--- 9 files changed, 459 insertions(+), 78 deletions(-) diff --git a/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt index 6bffe16..0c74eb3 100644 --- a/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt +++ b/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt @@ -44,9 +44,29 @@ private var currentGroup: ExpressGroup? = null init { + // 设置固定高度和禁用嵌套滚动来解决滑动问题 binding.rvPackages.apply { - layoutManager = LinearLayoutManager(context) + layoutManager = object : LinearLayoutManager(context) { + override fun canScrollVertically(): Boolean { + // 禁用内部RecyclerView的垂直滚动 + return false + } + + // 确保测量所有子项,防止部分内容不可见 + override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) { + try { + super.onLayoutChildren(recycler, state) + } catch (e: IndexOutOfBoundsException) { + // 捕获可能的异常,防止崩溃 + } + } + } adapter = packagesAdapter + // 禁用嵌套滚动,让外部RecyclerView处理所有滚动 + isNestedScrollingEnabled = false + // 启用回收视图缓存 + setItemViewCacheSize(20) + setHasFixedSize(true) } } @@ -54,7 +74,13 @@ currentGroup = group binding.tvStationName.text = group.stationName binding.tvPackageCount.text = "共${group.packages.size}个包裹" + + // 确保所有数据都被更新 + packagesAdapter.submitList(null) packagesAdapter.submitList(group.packages) + + // 请求布局刷新 + binding.rvPackages.requestLayout() } fun setOnPackageClickListener(listener: (ExpressGroup, ExpressPackage) -> Unit) { @@ -80,13 +106,21 @@ holder.bind(pack) } + // 防止部分内容不显示 + override fun getItemCount(): Int { + return currentList.size + } + inner class ViewHolder(private val binding: ItemExpressPackageHomeBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.root.setOnClickListener { - val pack = getItem(adapterPosition) - onPackageClick(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackageClick(pack) + } } } @@ -125,13 +159,21 @@ holder.bind(pack) } + // 防止部分内容不显示 + override fun getItemCount(): Int { + return currentList.size + } + inner class ViewHolder(private val binding: ItemPackageBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.ivPackageStatus.setOnClickListener { - val pack = getItem(adapterPosition) - onPackagePickup(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackagePickup(pack) + } } binding.root.setOnClickListener(null) diff --git a/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt index b0e39a4..16bbf8d 100644 --- a/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt +++ b/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt @@ -46,9 +46,29 @@ private var currentGroup: FinanceGroup? = null init { + // 设置固定高度和禁用嵌套滚动来解决滑动问题 binding.rvPackages.apply { - layoutManager = LinearLayoutManager(context) + layoutManager = object : LinearLayoutManager(context) { + override fun canScrollVertically(): Boolean { + // 禁用内部RecyclerView的垂直滚动 + return false + } + + // 确保测量所有子项,防止部分内容不可见 + override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) { + try { + super.onLayoutChildren(recycler, state) + } catch (e: IndexOutOfBoundsException) { + // 捕获可能的异常,防止崩溃 + } + } + } adapter = packagesAdapter + // 禁用嵌套滚动,让外部RecyclerView处理所有滚动 + isNestedScrollingEnabled = false + // 启用回收视图缓存 + setItemViewCacheSize(20) + setHasFixedSize(true) } } @@ -56,7 +76,13 @@ currentGroup = group binding.tvStationName.text = group.stationName binding.tvPackageCount.text = "共${group.packages.size}笔账单" + + // 确保所有数据都被更新 + packagesAdapter.submitList(null) packagesAdapter.submitList(group.packages) + + // 请求布局刷新 + binding.rvPackages.requestLayout() } fun setOnPackageClickListener(listener: (FinanceGroup, FinancePackage) -> Unit) { @@ -81,14 +107,22 @@ val pack = getItem(position) holder.bind(pack) } + + // 防止部分内容不显示 + override fun getItemCount(): Int { + return currentList.size + } inner class ViewHolder(private val binding: ItemFinancePackageHomeBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.root.setOnClickListener { - val pack = getItem(adapterPosition) - onPackageClick(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackageClick(pack) + } } } @@ -126,14 +160,22 @@ val pack = getItem(position) holder.bind(pack) } + + // 防止部分内容不显示 + override fun getItemCount(): Int { + return currentList.size + } inner class ViewHolder(private val binding: ItemFinanceBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.ivPackageStatus.setOnClickListener { - val pack = getItem(adapterPosition) - onPackagePickup(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackagePickup(pack) + } } binding.root.setOnClickListener(null) diff --git a/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt index 2c1365f..e39c845 100644 --- a/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt +++ b/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt @@ -47,8 +47,23 @@ init { binding.rvPackages.apply { - layoutManager = LinearLayoutManager(context) + layoutManager = object : LinearLayoutManager(context) { + override fun canScrollVertically(): Boolean { + return false + } + + override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) { + try { + super.onLayoutChildren(recycler, state) + } catch (e: IndexOutOfBoundsException) { + // 捕获可能的异常,防止崩溃 + } + } + } adapter = packagesAdapter + isNestedScrollingEnabled = false + setItemViewCacheSize(20) + setHasFixedSize(true) } } @@ -56,7 +71,11 @@ currentGroup = group binding.tvStationName.text = group.stationName binding.tvPackageCount.text = "共${group.packages.size}张机票" + + packagesAdapter.submitList(null) packagesAdapter.submitList(group.packages) + + binding.rvPackages.requestLayout() } fun setOnPackageClickListener(listener: (FlightGroup, FlightPackage) -> Unit) { @@ -82,13 +101,20 @@ holder.bind(pack) } + override fun getItemCount(): Int { + return currentList.size + } + inner class ViewHolder(private val binding: ItemFlightPackageHomeBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.root.setOnClickListener { - val pack = getItem(adapterPosition) - onPackageClick(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackageClick(pack) + } } } @@ -127,13 +153,20 @@ holder.bind(pack) } + override fun getItemCount(): Int { + return currentList.size + } + inner class ViewHolder(private val binding: ItemFlightBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.ivPackageStatus.setOnClickListener { - val pack = getItem(adapterPosition) - onPackagePickup(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackagePickup(pack) + } } binding.root.setOnClickListener(null) diff --git a/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt index 856ba45..67ba7d7 100644 --- a/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt +++ b/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt @@ -42,8 +42,23 @@ init { binding.rvPackages.apply { - layoutManager = LinearLayoutManager(context) + layoutManager = object : LinearLayoutManager(context) { + override fun canScrollVertically(): Boolean { + return false + } + + override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) { + try { + super.onLayoutChildren(recycler, state) + } catch (e: IndexOutOfBoundsException) { + // 捕获可能的异常,防止崩溃 + } + } + } adapter = packagesAdapter + isNestedScrollingEnabled = false + setItemViewCacheSize(20) + setHasFixedSize(true) } } @@ -51,7 +66,11 @@ currentGroup = group binding.tvStationName.text = group.stationName binding.tvPackageCount.text = "共${group.packages.size}笔收入" + + packagesAdapter.submitList(null) packagesAdapter.submitList(group.packages) + + binding.rvPackages.requestLayout() } } } @@ -71,13 +90,20 @@ holder.bind(pack) } + override fun getItemCount(): Int { + return currentList.size + } + inner class ViewHolder(private val binding: ItemIncomePackageHomeBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.root.setOnClickListener { - val pack = getItem(adapterPosition) - onPackageClick(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackageClick(pack) + } } } diff --git a/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt index 0efd02f..fb7490a 100644 --- a/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt +++ b/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt @@ -47,8 +47,23 @@ init { binding.rvPackages.apply { - layoutManager = LinearLayoutManager(context) + layoutManager = object : LinearLayoutManager(context) { + override fun canScrollVertically(): Boolean { + return false + } + + override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) { + try { + super.onLayoutChildren(recycler, state) + } catch (e: IndexOutOfBoundsException) { + // 捕获可能的异常,防止崩溃 + } + } + } adapter = packagesAdapter + isNestedScrollingEnabled = false + setItemViewCacheSize(20) + setHasFixedSize(true) } } @@ -56,7 +71,11 @@ currentGroup = group binding.tvStationName.text = group.stationName binding.tvPackageCount.text = "共${group.packages.size}张车票" + + packagesAdapter.submitList(null) packagesAdapter.submitList(group.packages) + + binding.rvPackages.requestLayout() } fun setOnPackageClickListener(listener: (TrainGroup, TrainPackage) -> Unit) { @@ -82,13 +101,20 @@ holder.bind(pack) } + override fun getItemCount(): Int { + return currentList.size + } + inner class ViewHolder(private val binding: ItemTrainPackageHomeBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.root.setOnClickListener { - val pack = getItem(adapterPosition) - onPackageClick(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackageClick(pack) + } } } @@ -127,13 +153,20 @@ holder.bind(pack) } + override fun getItemCount(): Int { + return currentList.size + } + inner class ViewHolder(private val binding: ItemTrainBinding) : RecyclerView.ViewHolder(binding.root) { init { binding.ivPackageStatus.setOnClickListener { - val pack = getItem(adapterPosition) - onPackagePickup(pack) + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + val pack = getItem(position) + onPackagePickup(pack) + } } binding.root.setOnClickListener(null) diff --git a/app/src/main/java/com/example/firstapp/database/service/ApiService.kt b/app/src/main/java/com/example/firstapp/database/service/ApiService.kt index 41c83c2..989348b 100644 --- a/app/src/main/java/com/example/firstapp/database/service/ApiService.kt +++ b/app/src/main/java/com/example/firstapp/database/service/ApiService.kt @@ -1,6 +1,7 @@ package com.example.firstapp.database.service import TokenResponse +import android.content.Context import com.example.firstapp.database.entity.ApiResponse import com.example.firstapp.database.entity.KeywordConfig import com.example.firstapp.database.request.ProductOrdersRequest @@ -16,6 +17,8 @@ import com.example.firstapp.model.CategoryConfig import com.example.firstapp.model.CategoryConfigSync import com.example.firstapp.network.AuthInterceptor +import com.example.firstapp.network.ResponseInterceptor +import com.example.firstapp.network.TokenExpiredInterceptor import okhttp3.MultipartBody import okhttp3.OkHttpClient import okhttp3.RequestBody @@ -72,9 +75,11 @@ @POST("api/account/close") suspend fun closeAccount(): AccountCloseResponse - fun getUserCategories(currentUserId: String): List<CategoryConfig> + @GET("api/categoryConfig/getByUserId/{userId}") + suspend fun getUserCategories(@Path("userId") currentUserId: String): List<CategoryConfig> - fun saveUserCategories(categoryConfigSync: CategoryConfigSync) + @POST("api/categoryConfig/saveOrUpdate/") + suspend fun saveUserCategories(@Body categoryConfigSync: CategoryConfigSync) } @@ -84,25 +89,37 @@ // private const val BASE_URL ="http://192.168.1.213:8080/flower/" private const val BASE_URL ="http://14.103.144.28:8080/flower/" + private lateinit var appContext: Context + + // 初始化方法,需要在Application中调用 + fun init(context: Context) { + appContext = context.applicationContext + } // 创建OkHttpClient,配置拦截器和超时时间 - private val okHttpClient = OkHttpClient.Builder() - .addInterceptor(AuthInterceptor()) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .build() + private val okHttpClient by lazy { + OkHttpClient.Builder() + .addInterceptor(AuthInterceptor()) + .addInterceptor(TokenExpiredInterceptor(appContext)) + .addInterceptor(ResponseInterceptor(appContext)) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .build() + } //添加Gson解析器,用于自动将JSON响应转换为Kotlin/Java对象 - private val retrofit = Retrofit - .Builder() - .client(okHttpClient) - .baseUrl(BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) - .build() + private val retrofit by lazy { + Retrofit + .Builder() + .client(okHttpClient) + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + } //通过动态代理技术创建ApiService接口的具体实现类 - val apiService:ApiService = retrofit.create(ApiService::class.java) + val apiService by lazy { retrofit.create(ApiService::class.java) } } diff --git a/app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt b/app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt new file mode 100644 index 0000000..4c8954d --- /dev/null +++ b/app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt @@ -0,0 +1,85 @@ +package com.example.firstapp.network + +import android.content.Context +import android.content.Intent +import android.widget.Toast +import com.example.firstapp.activity.LoginActivity +import com.example.firstapp.utils.PreferencesManager +import com.google.gson.JsonParser +import okhttp3.Interceptor +import okhttp3.Response +import okhttp3.ResponseBody +import okio.Buffer +import kotlin.concurrent.thread + +/** + * 响应拦截器 - 处理业务错误码 + * 根据后端提供的接口规范,检测token失效的情况 + */ +class ResponseInterceptor(private val context: Context) : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val response = chain.proceed(request) + + try { + // 只处理成功的响应 + if (response.isSuccessful) { + val responseBody = response.body + if (responseBody != null) { + val source = responseBody.source() + source.request(Long.MAX_VALUE) + val buffer = source.buffer.clone() + val responseBodyString = buffer.readUtf8() + + try { + // 解析JSON + val jsonObject = JsonParser.parseString(responseBodyString).asJsonObject + + // 检查业务状态码 + if (jsonObject.has("code")) { + val code = jsonObject.get("code").asString + + // 如果状态码表示token失效或登录过期 + if (code == "401" || code == "-1" || code == "1001") { + // 检查返回的错误消息 + val message = if (jsonObject.has("msg")) jsonObject.get("msg").asString else "登录已失效,请重新登录" + + if (message.contains("token") || + message.contains("登录") || + message.contains("认证") || + message.contains("授权")) { + + // 清除本地token + PreferencesManager.clearUserData() + + // 在主线程中显示提示并跳转到登录页面 + thread { + android.os.Handler(context.mainLooper).post { + Toast.makeText(context, message, Toast.LENGTH_LONG).show() + + // 创建跳转到登录页面的Intent,并添加清除任务栈的标志 + val intent = Intent(context, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + context.startActivity(intent) + } + } + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + + // 因为我们已经读取了响应体,需要重新创建一个新的响应体 + val newResponseBody = ResponseBody.create(responseBody.contentType(), responseBodyString) + return response.newBuilder().body(newResponseBody).build() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + + return response + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt b/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt index 1c73d50..dcc4a2f 100644 --- a/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt +++ b/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt @@ -1,19 +1,54 @@ package com.example.firstapp.network +import android.content.Context +import android.content.Intent +import android.widget.Toast +import com.example.firstapp.activity.LoginActivity import com.example.firstapp.utils.PreferencesManager import okhttp3.Interceptor import okhttp3.Response +import kotlin.concurrent.thread -class TokenExpiredInterceptor : Interceptor { +class TokenExpiredInterceptor(private val context: Context) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val response = chain.proceed(chain.request()) - // 如果返回401,说明token可能过期 - if (response.code == 401) { - PreferencesManager.clearUserData() // 清除本地token - // TODO: 处理token过期,例如跳转到登录页面 + // 如果返回401或后端自定义的token失效状态码,说明token可能过期 + if (response.code == 401 || isTokenInvalid(response)) { + // 清除本地token + PreferencesManager.clearUserData() + + // 在主线程中显示提示并跳转到登录页面 + thread { + android.os.Handler(context.mainLooper).post { + Toast.makeText(context, "登录已失效,请重新登录", Toast.LENGTH_LONG).show() + + // 创建跳转到登录页面的Intent,并添加清除任务栈的标志 + val intent = Intent(context, LoginActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + context.startActivity(intent) + } + } } return response } + + // 检查响应是否表示token失效 + private fun isTokenInvalid(response: Response): Boolean { + try { + // 尝试读取响应体,检查自定义的错误码 + // 注意:这会消耗响应体,如果需要在后续处理中使用响应体,需要克隆 + val responseBody = response.peekBody(4096).string() + + // 根据您的后端逻辑,检查是否包含token失效的提示 + // 这里假设后端在返回JSON中包含了错误码和消息 + return responseBody.contains("\"code\":\"401\"") || + responseBody.contains("token失效") || + responseBody.contains("请重新登录") + } catch (e: Exception) { + return false + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt index 45b5b0a..b367f26 100644 --- a/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt @@ -50,8 +50,9 @@ val unreadReminderCount: LiveData<Int> = _unreadReminderCount private lateinit var secureStorage: SecureStorage - private lateinit var currentUserId: String + private var currentUserId: String = "" private lateinit var reminderRecordRepository: ReminderRecordRepository + private var categoriesLoaded = false init { // 初始化时加载包裹列表数据 @@ -64,7 +65,13 @@ secureStorage = SecureStorage(context) currentUserId = userId reminderRecordRepository = ReminderRecordRepository(context) - loadCategories() + + // 只在首次加载或者用户修改分类后加载分类数据 + if (!categoriesLoaded) { + loadCategories() + categoriesLoaded = true + } + // 初始化时更新可见分类 _categories.value?.let { updateVisibleCategories(it) } // 加载未读提醒数量 @@ -188,9 +195,6 @@ private fun loadCategories() { viewModelScope.launch { try { - // 先尝试从本地获取配置 - val localCategories = secureStorage.getCategories(currentUserId) - // 默认完整分类列表 val fullCategories = listOf( CategoryConfig(1, "快递", 0, true), @@ -206,30 +210,73 @@ CategoryConfig(2, "还款", 1, true) ) - if (localCategories.isNotEmpty()) { - // 如果本地有配置,直接使用本地配置 - _categories.value = localCategories - } else { - try { - // 尝试从服务器获取用户信息判断是否是会员 - val savedPhone = PreferencesManager.getPhone() - val response = RetrofitClient.apiService.getUserInfo(savedPhone ?: "") - val isMember = response.code == "0" && response.data?.isMember == true + try { + // 获取会员状态 + val savedPhone = PreferencesManager.getPhone() + val userResponse = RetrofitClient.apiService.getUserInfo(savedPhone ?: "") + val isMember = userResponse.code == "0" && userResponse.data?.isMember == true + + // 从用户信息中获取正确的userId + if (userResponse.code == "0" && userResponse.data != null) { + currentUserId = userResponse.data?.id.toString() + } - // 根据会员状态设置默认分类 - val defaultCategories = if (isMember) fullCategories else basicCategories - _categories.value = defaultCategories - secureStorage.saveCategories(currentUserId, defaultCategories) - - // 同步到服务器 + // 首先检查本地是否有缓存的分类配置 + val localCategories = secureStorage.getCategories(currentUserId) + + if (localCategories.isNotEmpty()) { + // 使用本地缓存的配置 + _categories.value = localCategories + } else { + // 本地无缓存,尝试从服务器获取 try { - syncCategoriesToServer(defaultCategories) + val serverCategories = RetrofitClient.apiService.getUserCategories(currentUserId) + + if (serverCategories.isNotEmpty()) { + // 服务器有配置,使用服务器配置 + // 如果不是会员,需要过滤掉会员专属分类 + val filteredCategories = if (isMember) { + serverCategories + } else { + serverCategories.filter { it.name == "快递" || it.name == "还款" } + } + + _categories.value = filteredCategories + // 同时更新本地缓存 + secureStorage.saveCategories(currentUserId, filteredCategories) + // 同步回服务器(如果有变化) + if (filteredCategories.size != serverCategories.size) { + syncCategoriesToServer(filteredCategories) + } + } else { + // 服务器返回空,根据会员状态设置默认分类 + val defaultCategories = if (isMember) fullCategories else basicCategories + _categories.value = defaultCategories + // 更新本地缓存 + secureStorage.saveCategories(currentUserId, defaultCategories) + // 同步到服务器 + syncCategoriesToServer(defaultCategories) + } } catch (e: Exception) { - Log.e("HomeViewModel", "Failed to sync categories: ${e.message}") + // 服务器获取失败,使用默认分类 + Log.e("HomeViewModel", "Failed to get categories from server: ${e.message}") + val defaultCategories = if (isMember) fullCategories else basicCategories + _categories.value = defaultCategories + secureStorage.saveCategories(currentUserId, defaultCategories) } - } catch (e: Exception) { - // 如果获取用户信息失败,使用基础分类 + } + } catch (e: Exception) { + // 网络连接失败,尝试从本地获取配置 + Log.e("HomeViewModel", "Failed to get user info: ${e.message}") + val localCategories = secureStorage.getCategories(currentUserId) + + if (localCategories.isNotEmpty()) { + // 使用本地缓存的配置 + _categories.value = localCategories + } else { + // 本地也没有配置,使用基础分类 _categories.value = basicCategories + // 更新本地缓存 secureStorage.saveCategories(currentUserId, basicCategories) } } @@ -239,20 +286,26 @@ } catch (e: Exception) { Log.e("HomeViewModel", "Failed to load categories: ${e.message}") + // 出现异常时,使用基础分类 + val basicCategories = listOf( + CategoryConfig(1, "快递", 0, true), + CategoryConfig(2, "还款", 1, true) + ) + _categories.value = basicCategories + // 更新可见分类 + updateVisibleCategories(basicCategories) } } } - private fun syncCategoriesToServer(categories: List<CategoryConfig>) { - viewModelScope.launch { - try { - RetrofitClient.apiService.saveUserCategories( - CategoryConfigSync(currentUserId, categories) - ) - } catch (e: Exception) { - // 同步失败,可以稍后重试或者显示提示 - Log.e("CategorySync", "Failed to sync categories: ${e.message}") - } + private suspend fun syncCategoriesToServer(categories: List<CategoryConfig>) { + try { + RetrofitClient.apiService.saveUserCategories( + CategoryConfigSync(currentUserId, categories) + ) + } catch (e: Exception) { + // 同步失败,可以稍后重试或者显示提示 + Log.e("CategorySync", "Failed to sync categories: ${e.message}") } } @@ -261,9 +314,17 @@ // 保存到本地存储 secureStorage.saveCategories(currentUserId, categories) // 同步到服务器 - syncCategoriesToServer(categories) + viewModelScope.launch { + try { + syncCategoriesToServer(categories) + } catch (e: Exception) { + Log.e("HomeViewModel", "Failed to sync categories: ${e.message}") + } + } // 更新可见分类 updateVisibleCategories(categories) + // 标记分类已被修改 + categoriesLoaded = true } private fun updateVisibleCategories(categories: List<CategoryConfig>) { @@ -287,10 +348,17 @@ } } + // 添加方法以强制刷新分类 + fun refreshCategories() { + categoriesLoaded = false + loadCategories() + } + // 登出时不再清除本地数据 fun logout() { // 只清除内存中的数据 _categories.value = emptyList() + categoriesLoaded = false } } \ No newline at end of file -- Gitblit v1.9.3