cloudroam
2025-03-31 a7820e2f1ee06a7b43b4d351cced3343d7e1a5e2
fix 登录限制
已删除1个文件
已修改11个文件
已添加13个文件
1054 ■■■■ 文件已修改
app/build.gradle 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/AndroidManifest.xml 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/MainActivity.kt 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/CategorySelectorAdapter.kt 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/request/SmsLoginRequest.kt 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/request/SmsSendRequest.kt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/response/OAuth2TokenResponse.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ApiService.kt 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ModelService.kt 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/RetrofitClient.kt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/CategoryConfig.kt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/CategoryConfigSync.kt 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt 211 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/util/CategoryDragCallback.kt 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/util/SecureStorage.kt 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/home_add.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/dialog_category_selector.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_home.xml 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_category_selector.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-night/themes.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-v23/themes.xml 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/themes.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle
@@ -283,7 +283,12 @@
    // 支付宝支付SDK
    api 'com.alipay.sdk:alipaysdk-android:+@aar'
    implementation 'com.google.android.material:material:1.4.0'
//    implementation 'com.google.android.material:material:1.4.0'
    // 加密SharedPreferences
    implementation "androidx.security:security-crypto:1.1.0-alpha06"
    // Material Design组件
    implementation 'com.google.android.material:material:1.9.0'
}
app/src/main/AndroidManifest.xml
@@ -84,12 +84,12 @@
        android:supportsRtl="true"
        android:theme="@style/Theme.FirstApp"
        tools:targetApi="31">
        <activity
            android:name=".ui.reminderOther.ReminderOtherAddActivity2"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:exported="false"
            android:label="@string/title_activity_reminder_other_add2"
            android:theme="@style/Theme.FirstApp.Fullscreen" />
<!--        <activity-->
<!--            android:name=".ui.reminderOther.ReminderOtherAddActivity2"-->
<!--            android:configChanges="orientation|keyboardHidden|screenSize"-->
<!--            android:exported="false"-->
<!--            android:label="@string/title_activity_reminder_other_add2"-->
<!--            android:theme="@style/Theme.FirstApp.Fullscreen" />-->
        <activity
            android:name=".activity.LoginActivity"
            android:exported="true"
app/src/main/java/com/example/firstapp/MainActivity.kt
@@ -26,6 +26,7 @@
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.entity.Msg
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.database.service.RetrofitModelClient
import com.example.firstapp.ui.home.HomeViewModel
import com.example.firstapp.utils.Log
import com.example.firstapp.workers.KeywordUpdateWorker
@@ -256,7 +257,7 @@
                    CoroutineScope(Dispatchers.IO).launch {
                        try {
                            // API调用移到synchronized块外
                            val response = RetrofitClient.apiService.processSms(mapOf("content" to messageBody))
                            val response = RetrofitModelClient.modelService.processSms(mapOf("content" to messageBody))
                            
                            // 数据库操作放在synchronized块内
                            synchronized(syncLock) {
app/src/main/java/com/example/firstapp/adapter/CategorySelectorAdapter.kt
对比新文件
@@ -0,0 +1,66 @@
package com.example.firstapp.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.databinding.ItemCategorySelectorBinding
import com.example.firstapp.model.CategoryConfig
import java.util.Collections
/**
 * Adapter for the category selector recycler view.分类选择的适配器
 */
class CategorySelectorAdapter : RecyclerView.Adapter<CategorySelectorAdapter.ViewHolder>() {
    private var categories = mutableListOf<CategoryConfig>()
    private var lastCheckedPosition = -1 // 记录最后一个选中的位置
    class ViewHolder(val binding: ItemCategorySelectorBinding) : RecyclerView.ViewHolder(binding.root)
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemCategorySelectorBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        )
        return ViewHolder(binding)
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val category = categories[position]
        holder.binding.apply {
            categoryName.text = category.name
            categoryCheckBox.isChecked = category.isEnabled
            // 如果只有一个选中的项,禁用其复选框
            categoryCheckBox.isEnabled = !(getEnabledCount() == 1 && category.isEnabled)
            categoryCheckBox.setOnCheckedChangeListener { _, isChecked ->
                if (isChecked || getEnabledCount() > 1) {
                    categories[position] = category.copy(isEnabled = isChecked)
                    notifyDataSetChanged() // 刷新所有项以更新禁用状态
                } else {
                    // 如果要取消选中且只有一个选中项,恢复选中状态
                    categoryCheckBox.isChecked = true
                }
            }
        }
    }
    private fun getEnabledCount(): Int {
        return categories.count { it.isEnabled }
    }
    override fun getItemCount() = categories.size
    fun moveItem(fromPosition: Int, toPosition: Int) {
        Collections.swap(categories, fromPosition, toPosition)
        notifyItemMoved(fromPosition, toPosition)
    }
    fun setCategories(newCategories: List<CategoryConfig>) {
        categories.clear()
        categories.addAll(newCategories)
        notifyDataSetChanged()
    }
    fun getCategories() = categories.toList()
}
app/src/main/java/com/example/firstapp/database/request/SmsLoginRequest.kt
对比新文件
@@ -0,0 +1,7 @@
package com.example.firstapp.database.request
data class SmsLoginRequest(
    val username: String,
    val smsCode: String,
    val userType: String,
)
app/src/main/java/com/example/firstapp/database/request/SmsSendRequest.kt
对比新文件
@@ -0,0 +1,6 @@
package com.example.firstapp.database.request
data class SmsSendRequest(
    val tel: String,
    val userType: String
)
app/src/main/java/com/example/firstapp/database/response/OAuth2TokenResponse.kt
对比新文件
@@ -0,0 +1,17 @@
import com.google.gson.annotations.SerializedName
data class OAuth2AccessToken(
    @SerializedName("access_token")
    val value: String,          // token值
    @SerializedName("token_type")
    val tokenType: String,      // token类型
    @SerializedName("refresh_token")
    val refreshToken: String,   // 刷新token
    val scope: String          // 作用域
)
data class TokenResponse(
    val code: String,          // 注意这里改为 String 类型
    val msg: String,
    val data: OAuth2AccessToken?
)
app/src/main/java/com/example/firstapp/database/service/ApiService.kt
@@ -1,25 +1,29 @@
package com.example.firstapp.database.service
import TokenResponse
import com.example.firstapp.database.entity.ApiResponse
import com.example.firstapp.database.entity.KeywordConfig
import com.example.firstapp.database.request.SmsLoginRequest
import com.example.firstapp.database.request.SmsSendRequest
import com.example.firstapp.database.response.AlipayOrderInfoResponse
import com.example.firstapp.database.response.ContentResponse
import com.example.firstapp.database.response.DictResponse
import com.example.firstapp.database.response.LoginResponse
import com.example.firstapp.database.response.SecurityResponse
import com.example.firstapp.database.response.SmsProcessResponse
import com.example.firstapp.database.response.UserInfo
import com.example.firstapp.model.CategoryConfig
import com.example.firstapp.model.CategoryConfigSync
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Body
/**
 * API调用接口
@@ -35,11 +39,11 @@
    @GET("sysDict/getByDictCodeAndItemText")
    suspend fun getDictValue(@Query("dictCode") dictCode: String, @Query("itemText") itemText: String): DictResponse
    @POST("sms/send-code")
    suspend fun sendVerificationCode(@Query("phone") phone: String): LoginResponse
    @POST("api/sms/send/code")
    suspend fun sendVerificationCode(@Body request: SmsSendRequest): LoginResponse
    @POST("sms/login")
    suspend fun verifyCode(@Query("phone") phone: String, @Query("code") code: String): LoginResponse
    @POST("api/login/customer/phone/v2")
    suspend fun verifyCode(@Body request: SmsLoginRequest): TokenResponse
    @GET("config-security/enable-list-all")
    suspend fun getSecurityList(): SecurityResponse
@@ -57,20 +61,26 @@
        @Part avatar: MultipartBody.Part?
    ): ApiResponse<Unit>
    @POST("process-sms")
    suspend fun processSms(@Body body: Map<String, String>): SmsProcessResponse
    fun getUserCategories(currentUserId: String): List<CategoryConfig>
    fun saveUserCategories(categoryConfigSync: CategoryConfigSync)
}
// 创建Retrofit实例(单例)
object RetrofitClient{
//    private const val BASE_URL ="http://192.168.1.213:8888/jshERP-boot/"
    private const val BASE_URL ="http://192.168.1.213:5000/"
    private const val BASE_URL ="http://192.168.1.213:8080/flower/"
    //添加Gson解析器,用于自动将JSON响应转换为Kotlin/Java对象
    private val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()
    private val retrofit = Retrofit
        .Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    //通过动态代理技术创建ApiService接口的具体实现类
    val apiService:ApiService = retrofit.create(ApiService::class.java)
}
}
app/src/main/java/com/example/firstapp/database/service/ModelService.kt
对比新文件
@@ -0,0 +1,33 @@
package com.example.firstapp.database.service
import com.example.firstapp.database.response.SmsProcessResponse
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.POST
/**
 * 模型相关接口
 */
interface ModelService {
    @POST("process-sms")
    suspend fun processSms(@Body body: Map<String, String>): SmsProcessResponse
}
// 创建Retrofit实例(单例)
object RetrofitModelClient {
    private const val BASE_URL = "http://192.168.1.213:5000/"
    //添加Gson解析器,用于自动将JSON响应转换为Kotlin/Java对象
    private val retrofit = Retrofit
        .Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    //通过动态代理技术创建ApiService接口的具体实现类
    val modelService: ModelService = retrofit.create(ModelService::class.java)
}
app/src/main/java/com/example/firstapp/database/service/RetrofitClient.kt
对比新文件
@@ -0,0 +1 @@
app/src/main/java/com/example/firstapp/model/CategoryConfig.kt
对比新文件
@@ -0,0 +1,11 @@
package com.example.firstapp.model
/**
 * Created by fanghaowei on 2025/03/27. 首页数据配置类
 */
data class CategoryConfig(
    val id: Int,
    val name: String,
    val order: Int,
    val isEnabled: Boolean = true
)
app/src/main/java/com/example/firstapp/model/CategoryConfigSync.kt
对比新文件
@@ -0,0 +1,6 @@
package com.example.firstapp.model
data class CategoryConfigSync(
    val userId: String,
    val categories: List<CategoryConfig>
)
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
@@ -13,6 +13,7 @@
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.entity.Msg
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.database.service.RetrofitModelClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -58,7 +59,7 @@
                CoroutineScope(Dispatchers.IO).launch {
                    try {
                        val response =
                            RetrofitClient.apiService.processSms(mapOf("content" to messageBody.toString()))
                            RetrofitModelClient.modelService.processSms(mapOf("content" to messageBody.toString()))
                        if (response.status == "success") {
                            // 获取当前时间
app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt
@@ -8,6 +8,8 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@@ -17,7 +19,10 @@
import com.example.firstapp.activity.PickupActivity
import com.example.firstapp.adapter.ExpressAdapter
import com.example.firstapp.adapter.FinanceAdapter
import com.example.firstapp.adapter.CategorySelectorAdapter
import com.example.firstapp.databinding.FragmentHomeBinding
import com.example.firstapp.databinding.DialogCategorySelectorBinding
import com.google.android.material.bottomsheet.BottomSheetDialog
class HomeFragment : Fragment() {
@@ -30,7 +35,9 @@
    private lateinit var homeViewModel: HomeViewModel
    private lateinit var expressAdapter: ExpressAdapter
    private lateinit var financeAdapter: FinanceAdapter
//    private lateinit var memorialAdapter: MemorialAdapter
    private lateinit var incomeAdapter: FinanceAdapter
    private lateinit var flightAdapter: FinanceAdapter
    private lateinit var trainAdapter: FinanceAdapter
    private lateinit var dataUpdateReceiver: BroadcastReceiver
    //onCreateView这个方法创建后被调用,通常是初始化视图组件和观察者
@@ -46,15 +53,18 @@
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //通过 ViewModelProvider 获取 HomeViewModel 的实例,以便在视图中使用。
        homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
        // 加载广告图片
        //loadAdvertisements()
        // 假设从某处获取用户ID
//        val userId = getUserId() // 需要实现这个方法
        val userId ="123456"
        homeViewModel.initialize(requireContext(), userId)
        //调用这个方法来设置 RecyclerView用于设置 RecyclerView 的布局和适配器。
        setupRecyclerViews()
        setupTabSwitching()
        //调用这个方法来观察 ViewModel 中的数据变化
        observeViewModelData()
        setupCategorySelector()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
@@ -107,13 +117,28 @@
                startActivity(intent)
            }
        }
//
//        // 纪念日列表
//        binding.memorialRecycler.apply {
//            layoutManager = LinearLayoutManager(context)
//            memorialAdapter = MemorialAdapter()
//            adapter = memorialAdapter
//        }
        // 添加新的 RecyclerView
        binding.incomeRecycler.apply {
            layoutManager = LinearLayoutManager(context)
            incomeAdapter = FinanceAdapter()
            adapter = incomeAdapter
            visibility = View.GONE
        }
        binding.flightRecycler.apply {
            layoutManager = LinearLayoutManager(context)
            flightAdapter = FinanceAdapter()
            adapter = flightAdapter
            visibility = View.GONE
        }
        binding.trainRecycler.apply {
            layoutManager = LinearLayoutManager(context)
            trainAdapter = FinanceAdapter()
            adapter = trainAdapter
            visibility = View.GONE
        }
    }
    private fun setupTabSwitching() {
@@ -121,45 +146,63 @@
            // 设置初始状态
            tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
            tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
            others.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
            // 快递标签点击事件
            tabExpress.setOnClickListener {
                hideAllRecyclers()
                expressRecycler.visibility = View.VISIBLE
                financeRecycler.visibility = View.GONE
                tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
                tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                others.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabExpress.textSize = 16f
                tabFinance.textSize = 14f
                others.textSize = 14f
                updateTabStyles(tabExpress)
                homeViewModel.loadExpressData()
            }
            
            // 财务标签点击事件
            tabFinance.setOnClickListener {
                expressRecycler.visibility = View.GONE
                hideAllRecyclers()
                financeRecycler.visibility = View.VISIBLE
                tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
                others.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabExpress.textSize = 14f
                tabFinance.textSize = 16f
                others.textSize = 14f
                // 在切换到财务标签时加载数据 - 添加这行
                updateTabStyles(tabFinance)
                homeViewModel.loadFinanceData()
            }
            // 其他标签点击事件
            others.setOnClickListener {
                expressRecycler.visibility = View.GONE
                financeRecycler.visibility = View.GONE
                tabExpress.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                tabFinance.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray))
                others.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_selected))
                tabExpress.textSize = 14f
                tabFinance.textSize = 14f
                others.textSize = 16f
            tabIncome.setOnClickListener {
                hideAllRecyclers()
                incomeRecycler.visibility = View.VISIBLE
                updateTabStyles(tabIncome)
                homeViewModel.loadIncomeData()
            }
            tabFlight.setOnClickListener {
                hideAllRecyclers()
                flightRecycler.visibility = View.VISIBLE
                updateTabStyles(tabFlight)
                homeViewModel.loadFlightData()
            }
            tabTrain.setOnClickListener {
                hideAllRecyclers()
                trainRecycler.visibility = View.VISIBLE
                updateTabStyles(tabTrain)
                homeViewModel.loadTrainData()
            }
        }
    }
    private fun hideAllRecyclers() {
        binding.apply {
            expressRecycler.visibility = View.GONE
            financeRecycler.visibility = View.GONE
            incomeRecycler.visibility = View.GONE
            flightRecycler.visibility = View.GONE
            trainRecycler.visibility = View.GONE
        }
    }
    private fun updateTabStyles(selectedTab: TextView) {
        binding.apply {
            val tabs = listOf(tabExpress, tabFinance, tabIncome, tabFlight, tabTrain)
            tabs.forEach { tab ->
                tab.setTextColor(ContextCompat.getColor(requireContext(),
                    if (tab == selectedTab) R.color.tab_selected else R.color.gray))
                tab.textSize = if (tab == selectedTab) 16f else 14f
            }
        }
    }
@@ -175,10 +218,56 @@
        homeViewModel.financeItems.observe(viewLifecycleOwner) { items ->
            financeAdapter.submitList(items)
        }
//
//        homeViewModel.memorialItems.observe(viewLifecycleOwner) { items ->
//            memorialAdapter.submitList(items)
//        }
        homeViewModel.incomeItems.observe(viewLifecycleOwner) { items ->
            incomeAdapter.submitList(items)
        }
        homeViewModel.flightItems.observe(viewLifecycleOwner) { items ->
            flightAdapter.submitList(items)
        }
        homeViewModel.trainItems.observe(viewLifecycleOwner) { items ->
            trainAdapter.submitList(items)
        }
        // 观察可见分类的变化
        homeViewModel.visibleCategories.observe(viewLifecycleOwner) { categories: List<String> ->
            binding.apply {
                // 隐藏所有标签
                tabExpress.visibility = View.GONE
                tabFinance.visibility = View.GONE
                tabIncome.visibility = View.GONE
                tabFlight.visibility = View.GONE
                tabTrain.visibility = View.GONE
                // 根据选中的分类显示对应的标签
                categories.forEachIndexed { index: Int, categoryName: String ->
                    when (categoryName) {
                        "快递" -> {
                            tabExpress.visibility = View.VISIBLE
                            if (index == 0) tabExpress.performClick()
                        }
                        "还款" -> {
                            tabFinance.visibility = View.VISIBLE
                            if (index == 0) tabFinance.performClick()
                        }
                        "收入" -> {
                            tabIncome.visibility = View.VISIBLE
                            if (index == 0) tabIncome.performClick()
                        }
                        "航班" -> {
                            tabFlight.visibility = View.VISIBLE
                            if (index == 0) tabFlight.performClick()
                        }
                        "火车票" -> {
                            tabTrain.visibility = View.VISIBLE
                            if (index == 0) tabTrain.performClick()
                        }
                    }
                }
            }
        }
    }
    override fun onResume() {
@@ -220,4 +309,40 @@
            .load("http://192.168.1.235:9999/advertisement/down.png")
            .into(binding.bottomAdBanner)
    }
    private fun setupCategorySelector() {
        binding.categoryButton.setOnClickListener {
            // TODO: 检查会员状态
            if (true) { // 临时设置为true,实际应该检查会员状态
                showCategorySelectorDialog()
            } else {
                // 显示会员提示
                Toast.makeText(requireContext(), "该功能仅对会员开放", Toast.LENGTH_SHORT).show()
            }
        }
    }
    private fun showCategorySelectorDialog() {
        val dialog = BottomSheetDialog(requireContext())
        val dialogBinding = DialogCategorySelectorBinding.inflate(layoutInflater)
        dialog.setContentView(dialogBinding.root)
        val adapter = CategorySelectorAdapter()
        dialogBinding.categoryRecyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            this.adapter = adapter
        }
        // 加载现有分类
        homeViewModel.categories.observe(viewLifecycleOwner) { categories ->
            adapter.setCategories(categories)
        }
        dialogBinding.saveButton.setOnClickListener {
            homeViewModel.saveCategories(adapter.getCategories())
            dialog.dismiss()
        }
        dialog.show()
    }
}
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
@@ -1,29 +1,59 @@
package com.example.firstapp.ui.home
import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.firstapp.core.Core
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.model.CategoryConfig
import com.example.firstapp.model.CategoryConfigSync
import com.example.firstapp.model.ExpressGroup
import com.example.firstapp.model.ExpressPackage
import com.example.firstapp.model.FinanceGroup
import com.example.firstapp.model.FinancePackage
import com.example.firstapp.util.SecureStorage
import kotlinx.coroutines.launch
class HomeViewModel : ViewModel() {
    private val _expressItems = MutableLiveData<List<ExpressGroup>>()
    private val _financeItems = MutableLiveData<List<FinanceGroup>>()
    private val _incomeItems = MutableLiveData<List<FinanceGroup>>()
    private val _flightItems = MutableLiveData<List<FinanceGroup>>()
    private val _trainItems = MutableLiveData<List<FinanceGroup>>()
    val expressItems: LiveData<List<ExpressGroup>> = _expressItems
    val financeItems: LiveData<List<FinanceGroup>> = _financeItems
    val incomeItems: LiveData<List<FinanceGroup>> = _incomeItems
    val flightItems: LiveData<List<FinanceGroup>> = _flightItems
    val trainItems: LiveData<List<FinanceGroup>> = _trainItems
    private val _categories = MutableLiveData<List<CategoryConfig>>()
    val categories: LiveData<List<CategoryConfig>> = _categories
    // 添加可见分类的 LiveData
    private val _visibleCategories = MutableLiveData<List<String>>()
    val visibleCategories: LiveData<List<String>> = _visibleCategories
    private lateinit var secureStorage: SecureStorage
    private lateinit var currentUserId: String
    init {
        // 初始化时加载包裹列表数据
        loadExpressData()
        // 初始化时不加载财务列表数据 0317
        //  loadFinanceData()
    }
    fun initialize(context: Context, userId: String) {
        secureStorage = SecureStorage(context)
        currentUserId = userId
        loadCategories()
        // 初始化时更新可见分类
        _categories.value?.let { updateVisibleCategories(it) }
    }
    fun loadExpressData() {
@@ -74,4 +104,142 @@
        }
    }
    fun loadIncomeData() {
        viewModelScope.launch {
            val stations = Core.reminder.getByType("收入")
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    FinancePackage(
                        id = code.id,
                        company = code.secondLevel,
                        trackingNumber = code.code,
                        createTime = code.createTime
                    )
                }
                FinanceGroup(stationName = station.nickname, packages = packages)
            }
            _incomeItems.postValue(groups)
        }
    }
    fun loadFlightData() {
        viewModelScope.launch {
            val stations = Core.reminder.getByType("航班")
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    FinancePackage(
                        id = code.id,
                        company = code.secondLevel,
                        trackingNumber = code.code,
                        createTime = code.createTime
                    )
                }
                FinanceGroup(stationName = station.nickname, packages = packages)
            }
            _flightItems.postValue(groups)
        }
    }
    fun loadTrainData() {
        viewModelScope.launch {
            val stations = Core.reminder.getByType("火车票")
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    FinancePackage(
                        id = code.id,
                        company = code.secondLevel,
                        trackingNumber = code.code,
                        createTime = code.createTime
                    )
                }
                FinanceGroup(stationName = station.nickname, packages = packages)
            }
            _trainItems.postValue(groups)
        }
    }
    fun loadCategories() {
        viewModelScope.launch {
            try {
                // 先尝试从服务器获取配置
                val serverCategories = RetrofitClient.apiService.getUserCategories(currentUserId)
                if (serverCategories.isNotEmpty()) {
                    _categories.value = serverCategories
                    secureStorage.saveCategories(currentUserId, serverCategories)
                } else {
                    // 如果服务器没有配置,尝试获取本地配置
                    val localCategories = secureStorage.getCategories(currentUserId)
                    if (localCategories.isEmpty()) {
                        // 如果本地也没有配置,使用默认配置
                        val defaultCategories = listOf(
                            CategoryConfig(1, "快递", 0),
                            CategoryConfig(2, "还款", 1),
                            CategoryConfig(3, "收入", 2),
                            CategoryConfig(4, "航班", 3),
                            CategoryConfig(5, "火车票", 4)
                        )
                        _categories.value = defaultCategories
                        syncCategoriesToServer(defaultCategories)
                    } else {
                        _categories.value = localCategories
                        syncCategoriesToServer(localCategories)
                    }
                }
            } catch (e: Exception) {
                // 如果网络请求失败,使用本地数据
                val localCategories = secureStorage.getCategories(currentUserId)
                _categories.value = localCategories.ifEmpty {
                    listOf(
                        CategoryConfig(1, "快递", 0),
                        CategoryConfig(2, "还款", 1),
                        CategoryConfig(3, "收入", 2),
                        CategoryConfig(4, "航班", 3),
                        CategoryConfig(5, "火车票", 4)
                    )
                }
            }
        }
    }
    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}")
            }
        }
    }
    fun saveCategories(categories: List<CategoryConfig>) {
        viewModelScope.launch {
            // 保存到本地
            secureStorage.saveCategories(currentUserId, categories)
            // 同步到服务器
            syncCategoriesToServer(categories)
            _categories.value = categories
            // 更新可见分类
            updateVisibleCategories(categories)
        }
    }
    private fun updateVisibleCategories(categories: List<CategoryConfig>) {
        val visibleNames = categories
            .filter { it.isEnabled }
            .sortedBy { it.order }
            .map { it.name }
        _visibleCategories.value = visibleNames
    }
    // 登出时不再清除本地数据
    fun logout() {
        // 只清除内存中的数据
        _categories.value = emptyList()
    }
}
app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt
@@ -5,8 +5,11 @@
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import androidx.lifecycle.ViewModel
import com.example.firstapp.database.request.SmsLoginRequest
import com.example.firstapp.database.request.SmsSendRequest
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.utils.Log
import com.example.firstapp.ui.home.HomeViewModel
class LoginViewModel : ViewModel() {
@@ -19,16 +22,26 @@
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading
    private lateinit var homeViewModel: HomeViewModel
    fun sendVerificationCode(phone: String) {
        viewModelScope.launch {
            _isLoading.value = true
            try {
//                val response = RetrofitClient.apiService.sendVerificationCode(phone)
//                if (response.code == 200) {
                // 创建 SmsSendRequest 对象
                val request = SmsSendRequest(
                    tel = phone,
                    userType = "customer"
                )
                //Retrofit 进行网络请求时,类名不需要完全一致,只要保证类的属性名称和类型与后端 DTO 对象的属性一致即可。
                //Retrofit + Gson 在序列化时会将对象转换为 JSON,后端 Spring 框架会将 JSON 反序列化为 SmsSendDTO 对象
                //HTTP 请求实际传输的是 JSON 格式的数据,而不是 Java/Kotlin 对象。
                val response = RetrofitClient.apiService.sendVerificationCode(request)
                if (response.code == 0) {
                    _loginMessage.value = "验证码已发送"
//                } else {
//                    _loginMessage.value = response.msg.ifEmpty { "发送验证码失败" }
//                }
                } else {
                    _loginMessage.value = response.msg.ifEmpty { "发送验证码失败" }
                }
            } catch (e: Exception) {
                Log.e("LoginError", "Login failed: ${e.message}", e)
                _loginMessage.value = "网络错误,请稍后重试"
@@ -42,12 +55,20 @@
        viewModelScope.launch {
            _isLoading.value = true
            try {
//                val response = RetrofitClient.apiService.verifyCode(phone, code)
//                if (response.code == 200 && response.data) {
                val request = SmsLoginRequest(
                    username = phone,
                    smsCode = code,
                    userType = "customer"
                )
                //HttpServletRequest request这是后端 Spring 框架中的一个特殊参数,
                //用于获取 HTTP 请求的相关信息(如请求头、Cookie 等),它会由 Spring 框架自动注入,不需要客户端显式传递。
                val response = RetrofitClient.apiService.verifyCode(request)
                if (response.code == "0" && response.data != null) {
                    saveToken(response.data.value)  // 这里获取的是 access_token
                    _loginState.value = true
//                } else {
//                    _loginMessage.value = response.msg.ifEmpty { "登录失败" }
//                }
                } else {
                    _loginMessage.value = response.msg.ifEmpty { "登录失败" }
                }
            } catch (e: Exception) {
                Log.e("LoginError", "Login failed: ${e.message}", e)
                _loginMessage.value = "网络错误,请稍后重试"
@@ -57,4 +78,17 @@
        }
    }
    fun logout() {
        viewModelScope.launch {
            // 不再清除用户数据,只执行登出操作
            homeViewModel.logout()
            // 其他登出操作...
        }
    }
    private fun saveToken(token: String) {
        // TODO: 实现token存储逻辑
        // 可能还需要存储 refresh_token
    }
}
app/src/main/java/com/example/firstapp/util/CategoryDragCallback.kt
对比新文件
@@ -0,0 +1,25 @@
package com.example.firstapp.util
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.adapter.CategorySelectorAdapter
/**
 * 拖拽回调
 */
class CategoryDragCallback(private val adapter: CategorySelectorAdapter) : ItemTouchHelper.Callback() {
    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
        return makeMovementFlags(dragFlags, 0)
    }
    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        adapter.moveItem(viewHolder.adapterPosition, target.adapterPosition)
        return true
    }
    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        // 不需要实现
    }
}
app/src/main/java/com/example/firstapp/util/SecureStorage.kt
对比新文件
@@ -0,0 +1,49 @@
package com.example.firstapp.util
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.google.gson.Gson
import com.example.firstapp.model.CategoryConfig
class SecureStorage(context: Context) {
    private val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
    private val sharedPreferences = EncryptedSharedPreferences.create(
        "secure_prefs",
        masterKeyAlias,
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    private val gson = Gson()
    private fun getStorageKey(userId: String): String {
        return "categories_$userId"
    }
    fun saveCategories(userId: String, categories: List<CategoryConfig>) {
        val json = gson.toJson(categories)
        sharedPreferences.edit().putString(getStorageKey(userId), json).apply()
    }
    fun getCategories(userId: String): List<CategoryConfig> {
        val json = sharedPreferences.getString(getStorageKey(userId), null)
        return if (json != null) {
            gson.fromJson(json, Array<CategoryConfig>::class.java).toList()
        } else {
            emptyList()
        }
    }
    // 清除指定用户的数据
    fun clearUserData(userId: String) {
        sharedPreferences.edit().remove(getStorageKey(userId)).apply()
    }
    // 清除所有数据
    fun clearAllData() {
        sharedPreferences.edit().clear().apply()
    }
}
app/src/main/res/drawable/home_add.xml
对比新文件
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">
  <path
      android:fillColor="#FF000000"
      android:pathData="M991.7,915.6V90.9c0,-36.9 -29.9,-66.8 -66.8,-66.8H99.1c-36.9,0 -66.8,29.9 -66.8,66.8v824.7c0,36.9 29.9,66.8 66.8,66.8h825.7c36.9,0 66.8,-29.9 66.8,-66.8zM822.8,503.2c0,30.9 -25.1,56 -56,56H568v198.9c0,30.9 -25.1,56 -56,56s-56,-25.1 -56,-56V559.2H257.2c-30.9,0 -56,-25.1 -56,-56s25.1,-56 56,-56h198.9V248.4c0,-30.9 25.1,-56 56,-56s56,25.1 56,56v198.9h198.9c30.9,0 56,25.1 56,56z"/>
</vector>
app/src/main/res/layout/dialog_category_selector.xml
对比新文件
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="12dp">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="选择分类"
        android:textSize="18sp"
        android:textStyle="bold"
        android:layout_marginBottom="8dp"/>
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/categoryRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/saveButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存"
        android:layout_marginTop="12dp"
        android:backgroundTint="#000000"
        android:textColor="#FFFFFF"/>
</LinearLayout>
app/src/main/res/layout/fragment_home.xml
@@ -1,117 +1,173 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
<ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="40dp"> <!-- 留出顶部广告位的高度 -->
    >
<!--    LinearLayout的作用是按照垂直或者水平方向排列其子视图-->
<!--    CardView组件是用于实现卡片式布局-->
<!--    RecyclerView 回收商视图 它使用适配器(Adapter)来管理数据的显示,-->
<!--    开发者可以根据自己的需求实现适配器的方法,将数据与视图进行绑定。-->
<!--    这使得 RecyclerView 能够轻松地处理各种类型的数据,并按照自定义的布局方式展示。-->
<!--    支持局部刷新 通知数据集变化-->
    <LinearLayout
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        android:layout_height="match_parent"
        android:layout_marginTop="40dp"> <!-- 留出顶部广告位的高度 -->
        >
        <!-- 快递/财务切换区域 -->
        <!--    LinearLayout的作用是按照垂直或者水平方向排列其子视图-->
        <!--    CardView组件是用于实现卡片式布局-->
        <!--    RecyclerView 回收商视图 它使用适配器(Adapter)来管理数据的显示,-->
        <!--    开发者可以根据自己的需求实现适配器的方法,将数据与视图进行绑定。-->
        <!--    这使得 RecyclerView 能够轻松地处理各种类型的数据,并按照自定义的布局方式展示。-->
        <!--    支持局部刷新 通知数据集变化-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginBottom="8dp">
            android:orientation="vertical">
            <TextView
                android:id="@+id/tabExpress"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="快递"
                android:gravity="center"
                android:padding="8dp"
                android:textSize="16sp"
                android:textStyle="bold"/>
            <TextView
                android:id="@+id/tabFinance"
                android:layout_width="0dp"
            <!-- 快递/财务切换区域 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="财务"
                android:gravity="center"
                android:padding="8dp"
                android:textSize="16sp"/>
                android:layout_marginBottom="8dp"
                android:layout_marginTop="8dp"
                android:layout_marginHorizontal="8dp"
                android:orientation="horizontal">
            <TextView
                android:id="@+id/others"
                android:layout_width="0dp"
                <TextView
                    android:id="@+id/tabExpress"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:padding="6dp"
                    android:text="快递"
                    android:textSize="14sp"
                    android:textStyle="bold" />
                <TextView
                    android:id="@+id/tabFinance"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:padding="6dp"
                    android:text="还款"
                    android:textSize="14sp" />
                <TextView
                    android:id="@+id/tabIncome"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:padding="6dp"
                    android:text="收入"
                    android:textSize="14sp" />
                <TextView
                    android:id="@+id/tabFlight"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:padding="6dp"
                    android:text="航班"
                    android:textSize="14sp" />
                <TextView
                    android:id="@+id/tabTrain"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:padding="6dp"
                    android:text="火车票"
                    android:textSize="14sp" />
                <ImageButton
                    android:id="@+id/categoryButton"
                    android:layout_width="28dp"
                    android:layout_height="28dp"
                    android:layout_gravity="center_vertical"
                    android:layout_marginStart="2dp"
                    android:layout_marginEnd="2dp"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:contentDescription="分类设置"
                    android:padding="4dp"
                    android:scaleType="fitCenter"
                    android:src="@drawable/home_add" />
            </LinearLayout>
            <!-- 内容区域 -->
            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="其他"
                android:gravity="center"
                android:padding="8dp"
                android:textSize="16sp"/>
                android:layout_margin="16dp">
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/express_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/finance_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/income_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp"
                    android:visibility="gone" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/flight_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp"
                    android:visibility="gone" />
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/train_recycler"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="8dp"
                    android:visibility="gone" />
            </FrameLayout>
            <!-- 底部广告位 -->
            <androidx.cardview.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:cardCornerRadius="8dp"
                app:cardElevation="2dp">
                <ImageView
                    android:id="@+id/bottomAdBanner"
                    android:layout_width="match_parent"
                    android:layout_height="80dp"
                    android:scaleType="centerCrop" />
            </androidx.cardview.widget.CardView>
        </LinearLayout>
        <!-- 内容区域 -->
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="16dp">
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/express_recycler"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="8dp"/>
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/finance_recycler"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="8dp"/>
        </FrameLayout>
        <!-- 底部广告位 -->
        <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:cardCornerRadius="8dp"
            app:cardElevation="2dp">
            <ImageView
                android:id="@+id/bottomAdBanner"
                android:layout_width="match_parent"
                android:layout_height="80dp"
                android:scaleType="centerCrop"/>
        </androidx.cardview.widget.CardView>
    </LinearLayout>
</ScrollView>
    </ScrollView>
    <!-- 顶部广告位 -->
    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
       >
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/adBanner"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:scaleType="centerCrop"
            android:src="@drawable/up"/>
            android:src="@drawable/up" />
    </androidx.cardview.widget.CardView>
    <!-- 在适当的位置添加 -->
</FrameLayout>
app/src/main/res/layout/item_category_selector.xml
对比新文件
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="8dp"
    android:gravity="center_vertical">
    <TextView
        android:id="@+id/categoryName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textSize="16sp"/>
    <CheckBox
        android:id="@+id/categoryCheckBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"/>
</LinearLayout>
app/src/main/res/values-night/themes.xml
文件已删除
app/src/main/res/values-v23/themes.xml
@@ -1,9 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.FirstApp" parent="Base.Theme.FirstApp">
        <!-- Transparent system bars for edge-to-edge. -->
        <item name="android:navigationBarColor">@android:color/transparent</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowLightStatusBar">?attr/isLightTheme</item>
    </style>
<!--    <style name="Theme.FirstApp" parent="Base.Theme.FirstApp">-->
<!--        &lt;!&ndash; Transparent system bars for edge-to-edge. &ndash;&gt;-->
<!--        <item name="android:navigationBarColor">@android:color/transparent</item>-->
<!--        <item name="android:statusBarColor">@android:color/transparent</item>-->
<!--        <item name="android:windowLightStatusBar">?attr/isLightTheme</item>-->
<!--    </style>-->
</resources>
app/src/main/res/values/themes.xml
@@ -19,9 +19,9 @@
    </style>
    <style name="Theme.FirstApp.Fullscreen" parent="Theme.FirstApp">
        <item name="android:actionBarStyle">@style/Widget.Theme.FirstApp.ActionBar.Fullscreen</item>
        <item name="android:windowActionBarOverlay">true</item>
        <item name="android:windowBackground">@null</item>
<!--        <item name="android:actionBarStyle">@style/Widget.Theme.FirstApp.ActionBar.Fullscreen</item>-->
<!--        <item name="android:windowActionBarOverlay">true</item>-->
<!--        <item name="android:windowBackground">@null</item>-->
    </style>
    <style name="ThemeOverlay.FirstApp.FullscreenContainer" parent="">