cloudroam
2025-02-28 1097c45d8d6aa4b74a50e8d9a99dedab73f2bbad
add: 优化首页自动同步历史短信,和实时刷新包裹列表
已修改13个文件
261 ■■■■ 文件已修改
app/src/main/java/com/example/firstapp/MainActivity.kt 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/MsgDao.kt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/model/ExpressGroup.kt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/reminder/ReminderSettingsFragment.kt 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/activity_phone_login.xml 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_reminder_settings.xml 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/navigation/mobile_navigation.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/MainActivity.kt
@@ -27,10 +27,19 @@
import androidx.work.WorkManager
import com.example.firstapp.activity.LoginActivity
import com.example.firstapp.adapter.MyAdapter
import com.example.firstapp.core.Core
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.entity.Msg
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
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
class MainActivity : AppCompatActivity() {
@@ -42,16 +51,18 @@
    private lateinit var adapter: MyAdapter
    private lateinit var homeViewModel: HomeViewModel
    // 权限请求代码
    private val permissionRequest = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        if (isGranted) {
            // 权限授予后注册短信监听器
    private val multiplePermissionRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        when {
            permissions.getOrDefault(Manifest.permission.RECEIVE_SMS, false) &&
                    permissions.getOrDefault(Manifest.permission.READ_SMS, false) -> {
                // 两个权限都获得授权
            registerSmsReceiver()
            syncRecentSms()
        } else {
            // 权限拒绝,提示用户
            Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
            }
            else -> {
                // 有权限被拒绝
                Toast.makeText(this, "需要短信读取和接收权限才能正常使用功能", Toast.LENGTH_SHORT).show()
            }
        }
    }
@@ -71,6 +82,7 @@
//        val navView: BottomNavigationView = binding.navView
        val navView = binding.navView
        val navController = findNavController(R.id.nav_host_fragment_activity_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        val appBarConfiguration = AppBarConfiguration(
@@ -82,10 +94,13 @@
        navView.setupWithNavController(navController)
        // 检查权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
            // 请求权限
            permissionRequest.launch(Manifest.permission.READ_SMS)
            syncRecentSms()
        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
            ))
        } else {
            // 权限已经授予,继续执行相关操作
            registerSmsReceiver()
@@ -121,7 +136,10 @@
//        }, filter)
    }
    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment_activity_main)
        return navController.navigateUp() || super.onSupportNavigateUp()
    }
    private fun registerSmsReceiver() {
//        应用启动时执行 registerSmsReceiver()
//        创建 SmsReceiver 实例
@@ -179,7 +197,8 @@
            val threeDaysAgo = calendar.timeInMillis
            val cursor = contentResolver.query(
                Uri.parse("content://sms/sent"),
//                Uri.parse("content://sms/sent"), //发送短信
                Uri.parse("content://sms/inbox"),
                arrayOf("address", "body", "date"),
                "date >= ?",
                arrayOf(threeDaysAgo.toString()),
@@ -188,22 +207,44 @@
            cursor?.use {
                while (cursor.moveToNext()) {
                    //手机号
                    val address = cursor.getString(cursor.getColumnIndexOrThrow("address"))
                    val body = cursor.getString(cursor.getColumnIndexOrThrow("body"))
                    val date = cursor.getLong(cursor.getColumnIndexOrThrow("date"))
                    //短信内容
                    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) {
                    // 使用正则表达式提取验证码
                    val regex = "\\d{4,6}".toRegex()
                    val matchResult = regex.find(body)
                    matchResult?.let { result ->
                        val code = result.value
                        // 使用 ViewModel 保存到数据库
                        println("address")
                        println(address)
                        println(code)
                        println(date)
                       // homeViewModel.saveCode(address, code, date)
                            // 转换为 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 msgId = Core.msg.insert(msg)
                                val code = Code(0, rule.type, 1, rule.content, 1, 1, msgId, code, dateString, "中通")
                                Core.code.insert(code)
                                android.util.Log.d("SMS_DEBUG", "历史短信已保存到数据库")
                            }else{
                                android.util.Log.d("SmsReceiver", "Received SMS code: 已存在相同记录,不保存")
                    }
                        }else{
                            android.util.Log.d("SmsReceiver", "Received SMS code: 没有匹配到历史短信内容")
                        }
                    }
                    // 发送广播通知数据已更新
                    val updateIntent = Intent("com.example.firstapp.DATA_UPDATED")
                    sendBroadcast(updateIntent)
                }
            }
        } catch (e: Exception) {
app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt
@@ -29,6 +29,7 @@
    private fun setupViews() {
        binding.apply {
            btnBack.setOnClickListener {
                startActivity(Intent(this@PhoneLoginActivity, LoginActivity::class.java))
                finish()
            }
app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt
@@ -48,4 +48,7 @@
        ORDER BY time DESC
    """)
    fun getByKeyword(keyword: String): List<Code>
    @Query("SELECT * FROM Code WHERE type = :content and code= :code and overtime = :dateString LIMIT 1")
    fun queryByTypeAndCodeAndDate(content: String, code: String, dateString: String): Code
}
app/src/main/java/com/example/firstapp/database/dao/MsgDao.kt
@@ -1,14 +1,12 @@
package com.example.firstapp.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction
import androidx.room.Update
import androidx.sqlite.db.SupportSQLiteQuery
import com.example.firstapp.database.entity.Msg
app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt
@@ -22,4 +22,8 @@
        return codeDao.getByKeyword(keyword)
    }
    fun queryByTypeAndCodeAndDate(content: String, code: String, dateString: String): Code {
        return codeDao.queryByTypeAndCodeAndDate(content,code,dateString)
    }
}
app/src/main/java/com/example/firstapp/model/ExpressGroup.kt
@@ -6,6 +6,7 @@
)
data class ExpressPackage(
//    var id: Long,
    val company: String,
    val trackingNumber: String,
    val date: String
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
@@ -17,8 +17,12 @@
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
class SmsReceiver : BroadcastReceiver() {
@@ -68,31 +72,32 @@
                        )
                    }
                    Log.d("RuleList", ruleList.toString())
                    for (rule in ruleList) {
                        val code = rule.extractCodeFromMessage(messageBody.toString())
                        if (code!==null) {
                            Log.d("SmsReceiver", "Received SMS code: ${code}")
                            // 获取当前时间
                            // 转换为 Date 对象
                            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对象,并保存在数据库中
                            // 将 LocalDateTime 转换为 Date
                            val date = Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())
                            // 如果需要格式化显示
                            val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
                            val overtime = sdf.format(date)
                            val existingCode = Core.code.queryByTypeAndCodeAndDate(rule.content, code, overtime)
                            if (existingCode == null) {
                            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: 已存在相同记录,不保存")
                            }
                        }else{
                            Log.d("SmsReceiver", "Received SMS code: 没有匹配到内容")
                        }
                    }
app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt
@@ -1,11 +1,15 @@
package com.example.firstapp.ui.home
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
@@ -28,6 +32,7 @@
    private lateinit var expressAdapter: ExpressAdapter
//    private lateinit var financeAdapter: FinanceAdapter
//    private lateinit var memorialAdapter: MemorialAdapter
    private lateinit var dataUpdateReceiver: BroadcastReceiver
    //onCreateView这个方法创建后被调用,通常是初始化视图组件和观察者
    override fun onCreateView(
@@ -49,6 +54,20 @@
        setupRecyclerViews()
        //调用这个方法来观察 ViewModel 中的数据变化
        observeViewModelData()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 创建广播接收器
        dataUpdateReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                if (intent.action == "com.example.firstapp.DATA_UPDATED") {
                    // 收到数据更新广播时重新加载数据
                    homeViewModel.loadExpressData()
                }
            }
        }
    }
    private fun setupRecyclerViews() {
@@ -100,6 +119,30 @@
//        }
    }
    override fun onResume() {
        super.onResume()
        // 使用 ContextCompat 注册广播接收器,并指定 RECEIVER_NOT_EXPORTED 标志
        ContextCompat.registerReceiver(
            requireContext(),
            dataUpdateReceiver,
            IntentFilter("com.example.firstapp.DATA_UPDATED"),
            ContextCompat.RECEIVER_NOT_EXPORTED
        )
        // 加载数据
        homeViewModel.loadExpressData()
    }
    override fun onPause() {
        super.onPause()
        try {
            // 取消注册广播接收器
            requireContext().unregisterReceiver(dataUpdateReceiver)
        } catch (e: Exception) {
            // 处理可能的异常
            e.printStackTrace()
        }
    }
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
@@ -50,6 +50,7 @@
            val groups = stations.map { station ->
                val packages = Core.code.getByKeyword(station.nickname).map { code ->
                    ExpressPackage(
//                        id = code.id, //ID
                        company = code.name, //快递公司
                        trackingNumber = code.code, // 取件码
                        date = code.overtime  //时间
app/src/main/java/com/example/firstapp/ui/reminder/ReminderSettingsFragment.kt
@@ -7,6 +7,7 @@
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
@@ -38,6 +39,8 @@
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        (activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
        setupSpinner()
        setupRecyclerView()
        setupClickListeners()
@@ -62,9 +65,9 @@
    }
    private fun setupClickListeners() {
        binding.btnClose.setOnClickListener {
            findNavController().navigateUp()
        }
//        binding.btnClose.setOnClickListener {
//            findNavController().navigateUp()
//        }
        binding.btnAddReminder.setOnClickListener {
            val type = binding.spinnerType.selectedItem.toString()
app/src/main/res/layout/activity_phone_login.xml
@@ -7,6 +7,12 @@
    android:orientation="vertical"
    android:padding="24dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal">
    <ImageButton
        android:id="@+id/btnBack"
        android:layout_width="48dp"
@@ -18,11 +24,12 @@
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
            android:layout_marginStart="8dp"
        android:text="手机登录"
        android:textSize="24sp"
        android:textStyle="bold"
        android:textColor="#333333" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
@@ -36,7 +43,7 @@
            android:layout_height="wrap_content"
            android:background="@null"
            android:hint="请输入手机号"
            android:text="177625318565"
            android:text="17712345678"
            android:inputType="phone"
            android:maxLength="11"
            android:textSize="16sp"
app/src/main/res/layout/fragment_reminder_settings.xml
@@ -6,31 +6,31 @@
    android:background="#FAFAFA">
    <!-- 标题栏 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="#FFFFFF"
        android:elevation="1dp">
<!--    <RelativeLayout-->
<!--        android:layout_width="match_parent"-->
<!--        android:layout_height="56dp"-->
<!--        android:background="#FFFFFF"-->
<!--        android:elevation="1dp">-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="新增短信提醒"
            android:textSize="17sp"
            android:textColor="#222222" />
<!--        <TextView-->
<!--            android:layout_width="wrap_content"-->
<!--            android:layout_height="wrap_content"-->
<!--            android:layout_centerInParent="true"-->
<!--            android:text="新增短信提醒"-->
<!--            android:textSize="17sp"-->
<!--            android:textColor="#222222" />-->
        <!-- 关闭按钮 -->
        <ImageButton
            android:id="@+id/btn_close"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:padding="12dp"
            android:src="@android:drawable/ic_menu_close_clear_cancel" />
    </RelativeLayout>
<!--        &lt;!&ndash; 关闭按钮 &ndash;&gt;-->
<!--        <ImageButton-->
<!--            android:id="@+id/btn_close"-->
<!--            android:layout_width="48dp"-->
<!--            android:layout_height="48dp"-->
<!--            android:layout_alignParentEnd="true"-->
<!--            android:layout_centerVertical="true"-->
<!--            android:background="?attr/selectableItemBackgroundBorderless"-->
<!--            android:padding="12dp"-->
<!--            android:src="@android:drawable/ic_menu_close_clear_cancel" />-->
<!--    </RelativeLayout>-->
    <!-- 内容区域 -->
    <LinearLayout
app/src/main/res/navigation/mobile_navigation.xml
@@ -43,12 +43,26 @@
    </fragment>
<!--    <fragment-->
<!--        android:id="@+id/reminderSettingsFragment"-->
<!--        android:name="com.example.firstapp.ui.reminder.ReminderSettingsFragment"-->
<!--        android:label="设置提醒"-->
<!--        tools:layout="@layout/fragment_reminder_settings" />-->
    <fragment
        android:id="@+id/reminderSettingsFragment"
        android:name="com.example.firstapp.ui.reminder.ReminderSettingsFragment"
        android:label="设置提醒"
        tools:layout="@layout/fragment_reminder_settings" />
        tools:layout="@layout/fragment_reminder_settings">
        <!-- 可以添加 popUpTo 属性来指定返回时的目标 -->
        <action
            android:id="@+id/action_reminderSettings_to_notifications"
            app:popUpTo="@id/navigation_notifications"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
    </fragment>
    <fragment