cloudroam
2025-03-27 4ed001ff84ed21bf32da85dbc4ba53ff2829e987
fix  修改表字段后版本,
已修改17个文件
已添加1个文件
716 ■■■■■ 文件已修改
app/src/main/java/com/example/firstapp/MainActivity.kt 213 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/PickupActivity.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/PackageAdapter.kt 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/Code.kt 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/response/SmsProcessResponse.kt 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ApiService.kt 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/CourierStat.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/DailyStat.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/reminder/ReminderSettingsFragment.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_home.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_notifications.xml 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_finance_group.xml 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_finance_package_home.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/MainActivity.kt
@@ -5,23 +5,18 @@
import android.provider.Telephony
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.example.firstapp.databinding.ActivityMainBinding
import com.example.firstapp.receiver.SmsReceiver
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
@@ -31,7 +26,6 @@
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.entity.Rule
import com.example.firstapp.ui.home.HomeViewModel
import com.example.firstapp.utils.Log
import com.example.firstapp.workers.KeywordUpdateWorker
@@ -40,14 +34,13 @@
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.time.ZoneId
class MainActivity : AppCompatActivity() {
    // 安全防护关键词数组
    private var securityKeywordsList = emptyList<String>()
@@ -58,23 +51,31 @@
    private lateinit var adapter: MyAdapter
    private lateinit var homeViewModel: HomeViewModel
    private val multiplePermissionRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
    private val multiplePermissionRequest =
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        when {
            permissions.getOrDefault(Manifest.permission.RECEIVE_SMS, false) &&
                    permissions.getOrDefault(Manifest.permission.READ_SMS, false) -> {
                permissions.getOrDefault(
                    Manifest.permission.RECEIVE_SMS,
                    false
                ) && permissions.getOrDefault(Manifest.permission.READ_SMS, false) -> {
                // 两个权限都获得授权
                registerSmsReceiver()
//                syncRecentSms()
                initializeSecurityKeywords()
//                initializeSecurityKeywords()
            }
            else -> {
                // 有权限被拒绝
                Toast.makeText(this, "需要短信读取和接收权限才能正常使用功能", Toast.LENGTH_SHORT).show()
                    Toast.makeText(
                        this, "需要短信读取和接收权限才能正常使用功能", Toast.LENGTH_SHORT
                    ).show()
            }
        }
    }
    private val syncLock = Object()
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
@@ -95,13 +96,18 @@
        navView.setupWithNavController(navController)
        // 检查权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) != android.content.pm.PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
        if (ContextCompat.checkSelfPermission(
                this, Manifest.permission.RECEIVE_SMS
            ) != android.content.pm.PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(
                this, Manifest.permission.READ_SMS
            ) != android.content.pm.PackageManager.PERMISSION_GRANTED
        ) {
            // 同时请求两个权限
            multiplePermissionRequest.launch(arrayOf(
                Manifest.permission.RECEIVE_SMS,
                Manifest.permission.READ_SMS
            ))
            multiplePermissionRequest.launch(
                arrayOf(
                    Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS
                )
            )
        } else {
            // 权限已经授予,继续执行相关操作
            registerSmsReceiver()
@@ -178,10 +184,7 @@
    private fun logout() {
        // 清除登录信息
        getSharedPreferences("user_info", Context.MODE_PRIVATE)
            .edit()
            .clear()
            .apply()
        getSharedPreferences("user_info", Context.MODE_PRIVATE).edit().clear().apply()
        // 跳转回登录页
        startActivity(Intent(this, LoginActivity::class.java))
@@ -189,19 +192,24 @@
    }
    // 初始化禁用词
    @RequiresApi(Build.VERSION_CODES.O)
    private fun initializeSecurityKeywords() {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                val response = RetrofitClient.apiService.getSecurityList()
                if (response.code == 200) {
                    securityKeywordsList = response.data.map { it.keyword }
                    android.util.Log.d("MainActivity", "securityKeywordsList: $securityKeywordsList")
                    android.util.Log.d(
                        "MainActivity", "securityKeywordsList: $securityKeywordsList"
                    )
                    // 确保在主线程中调用 syncRecentSms
                    runOnUiThread {
                        syncRecentSms()
                    }
                } else {
                    android.util.Log.e("MainActivity", "Failed to get security list: ${response.code}")
                    android.util.Log.e(
                        "MainActivity", "Failed to get security list: ${response.code}"
                    )
                }
            } catch (e: Exception) {
                android.util.Log.e("MainActivity", "Error fetching security list", e)
@@ -209,15 +217,14 @@
        }
    }
    @RequiresApi(Build.VERSION_CODES.O)
    private fun syncRecentSms() {
        try {
            val calendar = Calendar.getInstance()
            calendar.add(Calendar.DAY_OF_YEAR, -3) // 获取3天前的时间
            calendar.add(Calendar.DAY_OF_YEAR, -3)
            val threeDaysAgo = calendar.timeInMillis
            val cursor = contentResolver.query(
//                Uri.parse("content://sms/sent"), //发送短信
                Uri.parse("content://sms/inbox"),
                arrayOf("address", "body", "date"),
                "date >= ?",
@@ -227,53 +234,149 @@
            cursor?.use {
                while (cursor.moveToNext()) {
                    //手机号
                    val address = cursor.getString(cursor.getColumnIndexOrThrow("address"))
                    //短信内容
                    val messageBody = cursor.getString(cursor.getColumnIndexOrThrow("body"))
                    //短信时间
                    val datetime = cursor.getLong(cursor.getColumnIndexOrThrow("date"))
                    // 这里我要写个数组,并创建个对象存放一些内容,如这个对象的属性有匹配内容,正则表达式,并循环遍历
                    val ruleList = mutableListOf(
                        Rule("快递","京东","\\d{6}"),
                        Rule("快递","菜鸟","\\d{1,2}-\\d{1,2}-\\d{4}")
                    )
                    for (rule in ruleList) {
                        val code = rule.extractCodeFromMessage(messageBody.toString())
                        if (code!==null) {
                            // 转换为 Date 对象
                            val date = Date(datetime)
                            // 如果需要格式化显示
                            val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
                            val dateString = sdf.format(date)
                            val existingCode = Core.code.queryByTypeAndCodeAndDate(rule.content, code, dateString)
                            if (existingCode == null) {
                                android.util.Log.d("SmsReceiver", "Received SMS code: ${code}")
                                val msg = Msg(0, "1111", "111111", messageBody.toString(), 1, "111", 1, 1)
                    // 保存原始短信
                    val msg = Msg(0, "1111", "111111", messageBody, 1, "111", 1, 1)
                                val msgId = Core.msg.insert(msg)
                                // 禁用关键词拦截,如果有禁用词则不保存在Code里面
                                android.util.Log.d("首页SmsReceiver", "securityKeywordsList: $securityKeywordsList")
                                if (securityKeywordsList.any { it in messageBody.toString() }) {
                                    android.util.Log.d("首页SmsReceiver", "Received SMS code: 禁用关键词拦截,不保存")
                    // 禁用关键词拦截
                    if (securityKeywordsList.any { it in messageBody }) {
                        android.util.Log.d("MainActivity", "历史短信含有禁用关键词,跳过处理")
                                    continue
                                }
                                val code = Code(0, rule.type, 1, rule.content, 1, 1, msgId, code, dateString, "中通",0,"","")
                    // 使用协程处理API调用和数据库操作
                    CoroutineScope(Dispatchers.IO).launch {
                        try {
                            // API调用移到synchronized块外
                            val response = RetrofitClient.apiService.processSms(mapOf("content" to messageBody))
                            // 数据库操作放在synchronized块内
                            synchronized(syncLock) {
                                if (response.status == "success") {
                                    when (response.data.category) {
                                        "快递" -> {
                                            val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                response.data.category,
                                                response.data.details.pickupCode ?: "",
                                                dateString
                                            )
                                            if (existingCode == null) {
                                                val code = Code(
                                                    id = 0,
                                                    category = response.data.category,
                                                    categoryId = 1,
                                                    typeId = 1,
                                                    ruleId = 1,
                                                    msgId = msgId,
                                                    createTime = dateString,
                                                    oneLevel = response.data.details.post ?: "",
                                                    secondLevel = response.data.details.company ?: "",
                                                    code = response.data.details.pickupCode ?: "",
                                                    pickup = 0,
                                                    pickupTime = "",
                                                    overTime = "",
                                                    address = response.data.details.address ?: "",
                                                    remarks = response.data.details.time ?: "",
                                                )
                                                if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                Core.code.insert(code)
                                android.util.Log.d("SMS_DEBUG", "历史短信已保存到数据库")
                                                }
                                                android.util.Log.d("MainActivity", "历史快递短信已保存: ${response.data.details.pickupCode}")
                            }else{
                                android.util.Log.d("SmsReceiver", "Received SMS code: 已存在相同记录,不保存")
                                                android.util.Log.d("MainActivity", "发现重复快递短信,跳过保存: ${response.data.details.pickupCode}")
                            }
                                        }
                                        "还款" -> {
                                            val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                response.data.category,
                                                response.data.details.amount ?: "",
                                                dateString
                                            )
                                            if (existingCode == null) {
                                                val code = Code(
                                                    id = 0,
                                                    category = response.data.category,
                                                    categoryId = 2,
                                                    typeId = 1,
                                                    ruleId = 2,
                                                    msgId = msgId,
                                                    createTime = dateString,
                                                    oneLevel = response.data.details.type ?: "",
                                                    secondLevel = response.data.details.bank ?: "",
                                                    code = response.data.details.amount ?: "",
                                                    pickup = 0,
                                                    pickupTime = "",
                                                    overTime = response.data.details.date ?: "",
                                                    address = response.data.details.address ?: "",
                                                    remarks = "最小还款金额${response.data.details.min_amount}还款卡号${response.data.details.number}"
                                                )
                                                if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                                    Core.code.insert(code)
                                                }
                                                android.util.Log.d("MainActivity", "历史还款短信已保存: ${response.data.details.amount}")
                        }else{
                            android.util.Log.d("SmsReceiver", "Received SMS code: 没有匹配到历史短信内容")
                                                android.util.Log.d("MainActivity", "发现重复还款短信,跳过保存: ${response.data.details.amount}")
                        }
                    }
                                        "收入" -> {
                                            val existingCode = Core.code.queryByTypeAndCodeAndDate(
                                                response.data.category,
                                                response.data.details.amount ?: "",
                                                dateString
                                            )
                                            if (existingCode == null) {
                                                val code = Code(
                                                    id = 0,
                                                    category = response.data.category,
                                                    categoryId = 3, // 3-收入类型
                                                    typeId = 1,     //暂时没有根据type分类
                                                    ruleId = 2,     //1-还款类型
                                                    msgId = msgId,
                                                    createTime = dateString,
                                                    oneLevel = response.data.details.bank ?: "",
                                                    secondLevel = response.data.details.bank ?: "",
                                                    code = response.data.details.amount ?: "",
                                                    pickup = 0, // 0-未取件,1-已取件
                                                    pickupTime = "", // 取件时间为空
                                                    overTime = response.data.details.datetime
                                                        ?: "",  // 超时时间为空,暂时没有这块处理逻辑
                                                    address = response.data.details.address ?: "",
                                                    remarks = "余额" + response.data.details.balance ?: "",
                                                )
                                                if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                                    Core.code.insert(code)
                                                }
                                                android.util.Log.d("MainActivity", "历史还款短信已保存: ${response.data.details.amount}")
                                            } else {
                                                android.util.Log.d("MainActivity", "发现重复还款短信,跳过保存: ${response.data.details.amount}")
                                            }
                                        }
                                    }
                    // 发送广播通知数据已更新
                    val updateIntent = Intent("com.example.firstapp.DATA_UPDATED")
                    sendBroadcast(updateIntent)
                }
            }
        } catch (e: Exception) {
                            android.util.Log.e("MainActivity", "处理历史短信出错: ${e.message}", e)
                        }
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Toast.makeText(this, "同步短信失败:${e.message}", Toast.LENGTH_SHORT).show()
        }
app/src/main/java/com/example/firstapp/activity/PickupActivity.kt
@@ -104,9 +104,9 @@
            val packages = codes.map { code ->
                ExpressPackage(
                    id = code.id,
                    company = code.name ?: company,
                    company = code.secondLevel ?: company,
                    trackingNumber = code.code,
                    createTime = code.createtime
                    createTime = code.createTime
                )
            }
            
app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt
@@ -26,9 +26,9 @@
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = getItem(position) // 使用 getItem 来获取当前位置的 item
        holder.tvTitle.text = item.type // 假设 Code 类有一个 `type` 属性
        holder.tvTitle.text = item.oneLevel // 假设 Code 类有一个 `type` 属性
        holder.tvDescription.text = item.code // 假设 Code 类有一个 `code` 属性
        var createtime  = "请注意:当前取件免费截止时间是"+item.createtime+",超时会收取额外费用"
        var createtime  = "请注意:当前取件免费截止时间是"+item.createTime+",超时会收取额外费用"
        holder.overTimeMsg.text = createtime
        println("打印......")
    }
app/src/main/java/com/example/firstapp/adapter/PackageAdapter.kt
@@ -45,13 +45,13 @@
        fun bind(code: Code) {
//            imgCourier.setImageResource(code.category)
            textCourierName.text = code.type
            textCourierName.text = code.oneLevel
            textTrackingNumber.text = code.code
            // 步骤1:定义解析器,将字符串转为 Date
            val parser = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
            val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
            try {
                val date: Date? = parser.parse(code.createtime)  // 解析字符串
                val date: Date? = parser.parse(code.createTime)  // 解析字符串
                date?.let {
                    textTime.text = "到货:"+formatter.format(it)  // 格式化并赋值
                } ?: run {
@@ -63,7 +63,7 @@
                textTime.text = "Format Error"
            }
            try {
                val date2: Date? = parser.parse(code.pickuptime)  // 解析字符串
                val date2: Date? = parser.parse(code.pickupTime)  // 解析字符串
                date2?.let {
                    textPickTime.text = "取件:"+formatter.format(it)  // 格式化并赋值
                } ?: run {
app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt
@@ -35,7 +35,7 @@
    @Query("SELECT * FROM Code")
     fun getAllCodes(): List<Code>
    @Query("SELECT * FROM Code WHERE type = :type")
    @Query("SELECT * FROM Code WHERE oneLevel = :type")
     fun getCodesByType(type: String): List<Code>
@@ -48,13 +48,13 @@
    @Query("""
        SELECT * FROM Code 
        WHERE type LIKE '%' || :keyword || '%'
        WHERE oneLevel LIKE '%' || :keyword || '%'
        AND pickup = '0'
        ORDER BY time DESC
    """)
    fun getByKeyword(keyword: String): List<Code>
    @Query("SELECT * FROM Code WHERE type = :content and code= :code and createtime = :dateString LIMIT 1")
    @Query("SELECT * FROM Code WHERE oneLevel = :content and code= :code and createTime = :dateString LIMIT 1")
    fun queryByTypeAndCodeAndDate(content: String, code: String, dateString: String): Code
@@ -62,23 +62,23 @@
    fun pickup(id: Long)
    //查询当天包裹信息
    @Query("SELECT * FROM code WHERE date(createtime) = date(:date/1000, 'unixepoch', 'localtime') ORDER BY createtime DESC")
    @Query("SELECT * FROM code WHERE date(createTime) = date(:date/1000, 'unixepoch', 'localtime') ORDER BY createTime DESC")
    fun getNewPackagesByDay(date: Long): List<Code>
    @Query("""
        SELECT * FROM code 
        WHERE substr(createtime, 1, 10) =
        WHERE substr(createTime, 1, 10) =
              date(:date/1000, 'unixepoch', 'localtime')
        ORDER BY createtime DESC
        ORDER BY createTime DESC
    """)
    fun getPackagesByDay(date: Long): Flow<List<Code>>
    @Query("""
        SELECT type as courierName, COUNT(*) as count
        SELECT oneLevel as courierName, COUNT(*) as count
        FROM code 
        WHERE strftime('%Y-%W', substr(createtime, 1, 10)) =
        WHERE strftime('%Y-%W', substr(createTime, 1, 10)) =
              strftime('%Y-%W', datetime(:date/1000, 'unixepoch', 'localtime'))
        GROUP BY type
        GROUP BY oneLevel
        ORDER BY count DESC
    """)
    fun getCourierStatsByWeek(date: Long): Flow<List<CourierStat>>
@@ -98,7 +98,7 @@
        COUNT(c.id) as count,
        date_value as weekStart
    FROM dates d
    LEFT JOIN code c ON strftime('%Y-%m-%d', c.createtime) BETWEEN
    LEFT JOIN code c ON strftime('%Y-%m-%d', c.createTime) BETWEEN
        strftime('%Y-%m-%d', d.date_value) 
        AND strftime('%Y-%m-%d', date(d.date_value, '+6 days'))
    GROUP BY d.date_value
@@ -109,28 +109,28 @@
    @Query("""
        SELECT * FROM code 
        WHERE substr(createtime, 1, 10) =
        WHERE substr(createTime, 1, 10) =
              date(:date/1000, 'unixepoch', 'localtime')
        ORDER BY createtime DESC
        ORDER BY createTime DESC
    """)
     fun getPackagesByWeek(date: Long): Flow<List<Code>>
    @Query("""
        SELECT type as courierName, COUNT(*) as count
        SELECT oneLevel as courierName, COUNT(*) as count
        FROM code 
        WHERE strftime('%Y-%m', substr(createtime, 1, 10)) =
        WHERE strftime('%Y-%m', substr(createTime, 1, 10)) =
              strftime('%Y-%m', datetime(:date/1000, 'unixepoch', 'localtime'))
        GROUP BY type
        GROUP BY oneLevel
        ORDER BY count DESC
    """)
    fun getCourierStatsByMonth(date: Long): Flow<List<CourierStat>>
    @Query("""
        SELECT type as courierName, COUNT(*) as count
        SELECT oneLevel as courierName, COUNT(*) as count
        FROM code 
        WHERE strftime('%Y', substr(createtime, 1, 10)) =
        WHERE strftime('%Y', substr(createTime, 1, 10)) =
              strftime('%Y', datetime(:date/1000, 'unixepoch', 'localtime'))
        GROUP BY type
        GROUP BY oneLevel
        ORDER BY count DESC
    """)
    fun getCourierStatsByYear(date: Long): Flow<List<CourierStat>>
@@ -148,7 +148,7 @@
//            COUNT(code.id) as count,
//            month_start as week_start
//        FROM months
//        LEFT JOIN code ON strftime('%Y-%m', code.createtime) = strftime('%Y-%m', months.month_start)
//        LEFT JOIN code ON strftime('%Y-%m', code.createTime) = strftime('%Y-%m', months.month_start)
//        GROUP BY months.month_start
//        ORDER BY months.month_start ASC
//    """)
@@ -167,7 +167,7 @@
            COUNT(code.id) as count,
            year_start as week_start
        FROM years 
        LEFT JOIN code ON strftime('%Y', code.createtime) = strftime('%Y', years.year_start)
        LEFT JOIN code ON strftime('%Y', code.createTime) = strftime('%Y', years.year_start)
        GROUP BY years.year_start
        ORDER BY years.year_start ASC
    """)
@@ -186,7 +186,7 @@
            CAST(strftime('%W', dates.date) AS INTEGER) as weekOfYear,
            COUNT(code.id) as count
        FROM dates 
        LEFT JOIN code ON date(code.createtime) = dates.date
        LEFT JOIN code ON date(code.createTime) = dates.date
        GROUP BY dates.date
        ORDER BY dates.date
    """)
@@ -205,7 +205,7 @@
            COUNT(code.id) as count,
            days.date as week_start
        FROM days 
        LEFT JOIN code ON date(code.createtime) = days.date
        LEFT JOIN code ON date(code.createTime) = days.date
        GROUP BY days.date
        ORDER BY days.date ASC
    """)
@@ -224,7 +224,7 @@
            COUNT(code.id) as count,
            month_start as weekStart
        FROM months 
        LEFT JOIN code ON strftime('%Y-%m', code.createtime) = strftime('%Y-%m', months.month_start)
        LEFT JOIN code ON strftime('%Y-%m', code.createTime) = strftime('%Y-%m', months.month_start)
        GROUP BY months.month_start
        ORDER BY months.month_start ASC
    """)
@@ -245,7 +245,7 @@
        COUNT(code.id) as count,
        days.date as weekStart
    FROM days 
    LEFT JOIN code ON date(code.createtime) = days.date
    LEFT JOIN code ON date(code.createTime) = days.date
    GROUP BY days.date
    ORDER BY days.date ASC
""")
@@ -258,7 +258,7 @@
        COUNT(id) as count,
        datetime(:date/1000, 'unixepoch', 'localtime') as weekStart
    FROM code 
    WHERE strftime('%Y', createtime) = strftime('%Y', datetime(:date/1000, 'unixepoch', 'localtime'))
    WHERE strftime('%Y', createTime) = strftime('%Y', datetime(:date/1000, 'unixepoch', 'localtime'))
""")
    fun getCurrentYearStats(date: Long): Flow<List<DailyStat>>
}
app/src/main/java/com/example/firstapp/database/entity/Code.kt
@@ -9,15 +9,17 @@
    @PrimaryKey(autoGenerate = true) val id: Long = 0, // 自增长的 id
    val category: String,
    val categoryId: Long,
    val type: String,
    val typeId: Long,
    val ruleId: Long,
    val msgId: Long,
    val code: String,
    var createtime: String,
    var name: String,
    var pickup: Int,
    var pickuptime: String,  //取件时间
    var overtime: String,   // 超期时间
    var createTime: String,
    val oneLevel: String,     //type改成oneLevel
    var secondLevel: String,  //name改成secondLevel
    val code: String,         //快递对应取件码、还款对应金额
    var pickup: Int,          //快递取件状态
    var pickupTime: String,   //快递取件时间
    var overTime: String,     //快递超期时间、还款日期
    var address: String,      //地址信息
    var remarks: String,      //备注信息保存
    var time: Date = Date(),
)
app/src/main/java/com/example/firstapp/database/response/SmsProcessResponse.kt
对比新文件
@@ -0,0 +1,31 @@
package com.example.firstapp.database.response
import com.google.gson.annotations.SerializedName
// 添加数据类来映射API响应
data class SmsProcessResponse(
    val status: String,
    val data: ProcessedData
)
data class ProcessedData(
    val category: String,
    val details: Details
)
data class Details(
    val post: String?,
    val company: String?,
    @SerializedName("pickup_code")
    val pickupCode: String?,
    val type: String?,
    val bank: String?,
    val amount: String?,
    val date: String?,
    val address: String?,
    val time: String?,
    val min_amount: String?,
    val number: String?,
    val datetime: String?,
    val balance: String?,
)
app/src/main/java/com/example/firstapp/database/service/ApiService.kt
@@ -7,11 +7,13 @@
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 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
@@ -54,12 +56,16 @@
        @Part("nickname") nickname: RequestBody,
        @Part avatar: MultipartBody.Part?
    ): ApiResponse<Unit>
    @POST("process-sms")
    suspend fun processSms(@Body body: Map<String, String>): SmsProcessResponse
}
// 创建Retrofit实例(单例)
object RetrofitClient{
    private const val BASE_URL ="http://192.168.1.198:8888/jshERP-boot/"
//    private const val BASE_URL ="http://192.168.1.213:8888/jshERP-boot/"
    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()
app/src/main/java/com/example/firstapp/model/CourierStat.kt
@@ -4,9 +4,9 @@
@DatabaseView(
    """
    SELECT type as courierName, COUNT(*) as count
    SELECT oneLevel as courierName, COUNT(*) as count
    FROM Code 
    GROUP BY type
    GROUP BY oneLevel
    """
)
data class CourierStat(
app/src/main/java/com/example/firstapp/model/DailyStat.kt
@@ -4,10 +4,10 @@
@DatabaseView(
    """
    SELECT substr(createtime, 1, 10) as date,
    SELECT substr(createTime, 1, 10) as date,
           COUNT(*) as count 
    FROM code 
    GROUP BY substr(createtime, 1, 10)
    GROUP BY substr(createTime, 1, 10)
    """
)
data class DailyStat(
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
@@ -12,16 +12,13 @@
import com.example.firstapp.core.Core
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.entity.Msg
import com.example.firstapp.database.repository.KeywordRepository
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.entity.Rule
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
@@ -33,24 +30,7 @@
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onReceive(context: Context, intent: Intent) {
        // 检查广播的 Action 是否为短信接收
        if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION == intent.action) {
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    val response = RetrofitClient.apiService.getSecurityList();
                    // 这里需要将response.data存放到变量中
                    if(response.code===200){
                        securityKeywordsList = response.data.map { it.keyword }
                        Log.d("SmsReceiver", "securityKeywordsList: $securityKeywordsList")
                    }
                }catch (e: Exception){
                    Log.d("SmsReceiver", "Error: ${e.message}")
                }
            }
            // 获取短信内容
            val bundle: Bundle? = intent.extras
            bundle?.let {
                val pdus = it.get("pdus") as Array<*>
@@ -62,7 +42,7 @@
                    messageBody.append(messages[i]?.messageBody)
                }
                // 输出短信内容到控制台
                // 保存原始短信
                val msg = Msg(0, "1111", "111111", messageBody.toString(),1, "111", 1, 1)
                val msgId = Core.msg.insert(msg)
@@ -74,125 +54,104 @@
                Log.d("SmsReceiver", "运行到正则匹配")
                // 这里我要写个数组,并创建个对象存放一些内容,如这个对象的属性有匹配内容,正则表达式,并循环遍历
                val ruleList = mutableListOf(
                    Rule("快递","京东","\\d{6}"),
                    Rule("快递","菜鸟驿站","\\d{1,2}-\\d{1,2}-\\d{4}"),
                    // 银行规则使用默认正则,实际匹配时会使用 BANK_PATTERNS 中的模式
                    Rule("财务", "中国银行", "账单金额[::](\\d+\\.?\\d*).*还款日[::](\\d{1,2})日"),
                    Rule("财务", "工商银行", "账单金额[::](\\d+\\.?\\d*).*还款日[::](\\d{1,2})日"),
                    Rule("财务", "建设银行", "账单金额[::](\\d+\\.?\\d*).*还款日[::](\\d{1,2})日"),
                    Rule("财务", "信用卡", "账单[¥¥](\\d+\\.?\\d*).*还款日(\\d{2})月(\\d{2})日"),
                    Rule("财务", "花呗", "本月花呗账单(\\d+\\.?\\d*)元.*还款日[期是::](\\d{1,2})[日号]")
                )
                // 调用API处理短信
                CoroutineScope(Dispatchers.IO).launch {
                    Log.d("SmsReceiver", "CoroutineScope started")
                    // 获取最新的关键词配置
                    val keywords = Core.keyword.getKeywords()
                    Log.d("keywords", keywords.toString())
                    keywords.forEach { keyword ->
                        ruleList.add(
                            Rule(
                                keyword.type,
                                keyword.keyword,
                              "\\d{1,2}-\\d{1,2}-\\d{4}"
                            )
                        )
                    }
                    Log.d("RuleList", ruleList.toString())
                    try {
                        val response =
                            RetrofitClient.apiService.processSms(mapOf("content" to messageBody.toString()))
                    for (rule in ruleList) {
                        val code = if (rule.type == "财务") {
                            // 对信用卡账单使用特殊的提取逻辑
                            val regex = rule.pattern.toRegex()
                            val matchResult = regex.find(messageBody.toString())
                            matchResult?.let {
                                val amount = it.groupValues[1]  // 账单金额
                                amount
                            }
                        } else {
                            rule.extractCodeFromMessage(messageBody.toString())
                        }
                        if (code !== null) {
                        if (response.status == "success") {
                            // 获取当前时间
                            val currentTime = LocalDateTime.now()
                            val date = Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())
                            val date =
                                Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())
                            val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
                            val createtime = sdf.format(date)
                            // 对于信用卡账单,使用还款日作为time字段
                            val time = if (rule.type == "财务") {
                                rule.extractDueDate(messageBody.toString()) ?: createtime
                            } else {
                                createtime
                            // 根据不同类型处理数据
                            when (response.data.category) {
                                "快递" -> {
                                    val code = Code(
                                        id = 0,
                                        category = response.data.category,
                                        categoryId = 1, // 1-快递类型
                                        typeId = 1,     //暂时没有根据type分类
                                        ruleId = 1,     //1-快递类型
                                        msgId = msgId,
                                        createTime = createtime,
                                        oneLevel = response.data.details.post ?: "",
                                        secondLevel = response.data.details.company ?: "",
                                        code = response.data.details.pickupCode ?: "",
                                        pickup = 0, // 0-未取件,1-已取件
                                        pickupTime = "", // 取件时间为空
                                        overTime = "",  // 超时时间为空,暂时没有这块处理逻辑
                                        address = response.data.details.address ?: "",
                                        remarks = response.data.details.time ?: "",
                                    )
                                    if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                        Core.code.insert(code)
                                    }
                            }
                            val existingCode = Core.code.queryByTypeAndCodeAndDate(rule.content, code, createtime)
                            if (existingCode == null) {
                                val codeEntity = Code(
                                    0,
                                    rule.type,
                                    1,
                                    rule.content,
                                    1,
                                    1,
                                    msgId,
                                    code,
                                    createtime,
                                    rule.content,  // 银行名称作为name
                                    0,
                                    time,  // 还款日期
                                    ""
                                "还款" -> {
                                    val code = Code(
                                        id = 0,
                                        category = response.data.category,
                                        categoryId = 2, // 2-还款类型
                                        typeId = 1,     //暂时没有根据type分类
                                        ruleId = 2,     //1-还款类型
                                        msgId = msgId,
                                        createTime = createtime,
                                        oneLevel = response.data.details.type ?: "",
                                        secondLevel = response.data.details.bank ?: "",
                                        code = response.data.details.amount ?: "",
                                        pickup = 0, // 0-未取件,1-已取件
                                        pickupTime = "", // 取件时间为空
                                        overTime = response.data.details.date
                                            ?: "",  // 超时时间为空,暂时没有这块处理逻辑
                                        address = response.data.details.address ?: "",
                                        remarks = "最小还款金额" + response.data.details.min_amount + "还款卡号" + response.data.details.number
                                            ?: "",
                                )
                                Core.code.insert(codeEntity)
                                Log.d("SMS_DEBUG", "新短信已保存到数据库")
                                    if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                        Core.code.insert(code)
                                    }
                                }
                                "收入" -> {
                                    val code = Code(
                                        id = 0,
                                        category = response.data.category,
                                        categoryId = 3, // 3-收入类型
                                        typeId = 1,     //暂时没有根据type分类
                                        ruleId = 2,     //1-还款类型
                                        msgId = msgId,
                                        createTime = createtime,
                                        oneLevel = response.data.details.bank ?: "",
                                        secondLevel = response.data.details.bank ?: "",
                                        code = response.data.details.amount ?: "",
                                        pickup = 0, // 0-未取件,1-已取件
                                        pickupTime = "", // 取件时间为空
                                        overTime = response.data.details.datetime
                                            ?: "",  // 超时时间为空,暂时没有这块处理逻辑
                                        address = response.data.details.address ?: "",
                                        remarks = "余额" + response.data.details.balance ?: "",
                                    )
                                    if(code.oneLevel!=""  && code.secondLevel!="" && code.code!="") {
                                        Core.code.insert(code)
                                    }
                                }
                            }
                                // 发送广播通知数据已更新
                                //"com.example.firstapp.DATA_UPDATED" 是一个自定义的广播 Action,相当于一个标识符或者说是一个频道名称。这个名称是我们自己定义的,通常使用应用的包名作为前缀,以避免与其他应用的广播冲突。
                                val updateIntent = Intent("com.example.firstapp.DATA_UPDATED")
                                context.sendBroadcast(updateIntent)
                                Log.d("SMS_DEBUG", "发送数据更新广播")
                            }else{
                                Log.d("SmsReceiver", "Received SMS code: 已存在相同记录,不保存")
                            Log.d("SMS_DEBUG", "新短信已保存到数据库")
                            }
                        }else{
                            Log.d("SmsReceiver", "Received SMS code: 没有匹配到内容")
                    } catch (e: Exception) {
                        Log.e("SmsReceiver", "Error processing SMS", e)
                        }
                    }
                }
                // kotlin 怎么创建一个类
//                for (rule in ruleList) {
//                    val code = rule.extractCodeFromMessage(messageBody.toString())
//
//                    if (code!==null) {
//                        Log.d("SmsReceiver", "Received SMS code: ${code}")
//
//
//                        // 获取当前时间
//                        val currentTime = LocalDateTime.now()
//                        // 加2小时
//                        val futureTime = currentTime.plusHours(2)
//                        // 定义时间格式
//                        val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
//                        // 转换为字符串
//                        val overtime = futureTime.format(formatter)
//                        // 封装成一个Code对象,并保存在数据库中
//                        val code = Code(0, rule.type,1, rule.content,1, 1,  msgId, code, overtime)
//                        Core.code.insert(code)
//                        Log.d("SMS_DEBUG", "新短信已保存到数据库")
//                        // 发送广播通知数据已更新
//                        //"com.example.firstapp.DATA_UPDATED" 是一个自定义的广播 Action,相当于一个标识符或者说是一个频道名称。这个名称是我们自己定义的,通常使用应用的包名作为前缀,以避免与其他应用的广播冲突。
//                        val updateIntent = Intent("com.example.firstapp.DATA_UPDATED")
//                        context.sendBroadcast(updateIntent)
//                        Log.d("SMS_DEBUG", "发送数据更新广播")
//                    }else{
//                        Log.d("SmsReceiver", "Received SMS code: 没有匹配到内容")
//                    }
//                }
            }
        }
    }
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
@@ -29,16 +29,16 @@
    fun loadExpressData() {
        viewModelScope.launch {
            // 1. 获取所有驿站类型的提醒设置
            val stations = Core.reminder.getByType("驿站")
            val stations = Core.reminder.getByType("快递")
            // 2. 按驿站分组获取包裹信息
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    ExpressPackage(
                        id = code.id, //ID
                        company = code.name, //快递公司
                        company = code.secondLevel, //快递公司
                        trackingNumber = code.code, // 取件码
                        createTime = code.createtime  //快递时间
                        createTime = code.createTime  //快递时间
                    )
                }
                ExpressGroup(
@@ -53,16 +53,16 @@
    fun loadFinanceData() {
        viewModelScope.launch {
            // 1. 获取所有驿站类型的提醒设置
            val stations = Core.reminder.getByType("财务")
            val stations = Core.reminder.getByType("还款")
            // 2. 按驿站分组获取包裹信息
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    FinancePackage(
                        id = code.id, //ID
                        company = code.name, //快递公司
                        company = code.secondLevel, //快递公司
                        trackingNumber = code.code, // 取件码
                        createTime = code.createtime  //快递时间
                        createTime = code.createTime  //快递时间
                    )
                }
                FinanceGroup(
app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt
@@ -130,37 +130,37 @@
    private fun setupClickListeners() {
        // 支付插件
        binding.payPlugin.setOnClickListener {
            // 跳转到支付插件页面
            lifecycleScope.launch {
                try {
                    val response = RetrofitClient.apiService.getPayOrderInfo()
                    var orderInfo=response.data
                    Log.d("AliPayHelper","获取订单信息时: ${response}")
                    // 这里调用支付宝
                    PayAbility.aliPay(requireActivity(), orderInfo, Observer {
                        when (it.resultStatus) {
                            "9000" -> {
//                                Snackbar.make(binding.root, "支付成功", Snackbar.LENGTH_LONG).show()
                                requireActivity().runOnUiThread {
                                    Toast.makeText(requireContext(), "支付成功", Toast.LENGTH_LONG).show()
                                }
                            }
                            else -> {
//                                Snackbar.make(binding.root, "支付失败", Snackbar.LENGTH_LONG).show()
                                requireActivity().runOnUiThread {
                                    Toast.makeText(requireContext(), "支付失败", Toast.LENGTH_LONG).show()
                                }
                            }
                        }
                    })
                } catch (e: Exception) {
                    Log.d("AliPayHelper","获取订单信息时发生错误: ${e.message}")
                }
            }
        }
//        binding.payPlugin.setOnClickListener {
//            // 跳转到支付插件页面
//            lifecycleScope.launch {
//                try {
//                    val response = RetrofitClient.apiService.getPayOrderInfo()
//                    var orderInfo=response.data
//                    Log.d("AliPayHelper","获取订单信息时: ${response}")
//                    // 这里调用支付宝
//                    PayAbility.aliPay(requireActivity(), orderInfo, Observer {
//                        when (it.resultStatus) {
//                            "9000" -> {
////                                Snackbar.make(binding.root, "支付成功", Snackbar.LENGTH_LONG).show()
//                                requireActivity().runOnUiThread {
//                                    Toast.makeText(requireContext(), "支付成功", Toast.LENGTH_LONG).show()
//                                }
//                            }
//                            else -> {
////                                Snackbar.make(binding.root, "支付失败", Snackbar.LENGTH_LONG).show()
//                                requireActivity().runOnUiThread {
//                                    Toast.makeText(requireContext(), "支付失败", Toast.LENGTH_LONG).show()
//                                }
//
//                            }
//                        }
//                    })
//
//                } catch (e: Exception) {
//                    Log.d("AliPayHelper","获取订单信息时发生错误: ${e.message}")
//                }
//            }
//        }
        // 设置提醒
@@ -210,7 +210,6 @@
        // 隐私协议
        binding.layoutPrivacy.setOnClickListener {
            startContentActivity("privacy_policy", "隐私协议")
            startActivity(intent)
        }
        // 使用教程
app/src/main/java/com/example/firstapp/ui/reminder/ReminderSettingsFragment.kt
@@ -48,7 +48,7 @@
    }
    private fun setupSpinner() {
        val types = arrayOf("驿站", "财务", "其他")
        val types = arrayOf("快递", "还款", "其他")
        val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, types)
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        binding.spinnerType.adapter = adapter
app/src/main/res/layout/fragment_home.xml
@@ -8,7 +8,7 @@
<ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="80dp"> <!-- 留出顶部广告位的高度 -->
    android:layout_marginTop="40dp"> <!-- 留出顶部广告位的高度 -->
    >
<!--    LinearLayout的作用是按照垂直或者水平方向排列其子视图-->
@@ -109,7 +109,7 @@
        <ImageView
            android:id="@+id/adBanner"
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:layout_height="40dp"
            android:scaleType="centerCrop"
            android:src="@drawable/up"/>
    </androidx.cardview.widget.CardView>
app/src/main/res/layout/fragment_notifications.xml
@@ -156,49 +156,49 @@
            android:paddingHorizontal="8dp"
            android:paddingVertical="4dp">
            <!-- 会员 -->
            <RelativeLayout
                android:id="@+id/memberVip"
                style="@style/PluginPay"
                android:padding="16dp">
<!--            &lt;!&ndash; 会员 &ndash;&gt;-->
<!--            <RelativeLayout-->
<!--                android:id="@+id/memberVip"-->
<!--                style="@style/PluginPay"-->
<!--                android:padding="16dp">-->
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:text="会员"
                    android:textColor="@android:color/black"
                    android:textSize="16sp" />
<!--                <TextView-->
<!--                    android:layout_width="wrap_content"-->
<!--                    android:layout_height="wrap_content"-->
<!--                    android:layout_centerVertical="true"-->
<!--                    android:text="会员"-->
<!--                    android:textColor="@android:color/black"-->
<!--                    android:textSize="16sp" />-->
                <ImageView
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:layout_alignParentEnd="true"
                    android:layout_centerVertical="true"
                    android:src="@drawable/right_forward" />
            </RelativeLayout>
<!--                <ImageView-->
<!--                    android:layout_width="24dp"-->
<!--                    android:layout_height="24dp"-->
<!--                    android:layout_alignParentEnd="true"-->
<!--                    android:layout_centerVertical="true"-->
<!--                    android:src="@drawable/right_forward" />-->
<!--            </RelativeLayout>-->
            <!-- 支付 -->
            <RelativeLayout
                android:id="@+id/payPlugin"
                style="@style/PluginPay"
                android:padding="16dp">
<!--            &lt;!&ndash; 支付 &ndash;&gt;-->
<!--            <RelativeLayout-->
<!--                android:id="@+id/payPlugin"-->
<!--                style="@style/PluginPay"-->
<!--                android:padding="16dp">-->
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:text="支付"
                    android:textColor="@android:color/black"
                    android:textSize="16sp" />
<!--                <TextView-->
<!--                    android:layout_width="wrap_content"-->
<!--                    android:layout_height="wrap_content"-->
<!--                    android:layout_centerVertical="true"-->
<!--                    android:text="支付"-->
<!--                    android:textColor="@android:color/black"-->
<!--                    android:textSize="16sp" />-->
                <ImageView
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:layout_alignParentEnd="true"
                    android:layout_centerVertical="true"
                    android:src="@drawable/right_forward" />
            </RelativeLayout>
<!--                <ImageView-->
<!--                    android:layout_width="24dp"-->
<!--                    android:layout_height="24dp"-->
<!--                    android:layout_alignParentEnd="true"-->
<!--                    android:layout_centerVertical="true"-->
<!--                    android:src="@drawable/right_forward" />-->
<!--            </RelativeLayout>-->
            <!-- 设置提醒 -->
            <LinearLayout
app/src/main/res/layout/item_finance_group.xml
@@ -1,12 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.card.MaterialCardView
    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="wrap_content"
    android:layout_marginHorizontal="12dp"
    android:layout_marginVertical="6dp"
    app:cardCornerRadius="8dp"
    app:cardElevation="2dp">
    app:cardElevation="2dp"
    app:cardBackgroundColor="@android:color/white"
    app:strokeColor="#FF000000"
    app:strokeWidth="2dp">
    <LinearLayout
        android:layout_width="match_parent"
@@ -51,4 +55,4 @@
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</androidx.cardview.widget.CardView>
</com.google.android.material.card.MaterialCardView>
app/src/main/res/layout/item_finance_package_home.xml
@@ -30,7 +30,7 @@
            android:id="@+id/tv_create_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textSize="10sp"
            android:textColor="#666666"
            android:layout_marginTop="4dp"/>
    </LinearLayout>