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) { // 权限授予后注册短信监听器 registerSmsReceiver() syncRecentSms() } else { // 权限拒绝,提示用户 Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show() 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, "需要短信读取和接收权限才能正常使用功能", 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,30 +72,31 @@ ) } 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对象,并保存在数据库中 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", "发送数据更新广播") // 将 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 @@ -26,8 +30,9 @@ private lateinit var homeViewModel: HomeViewModel private lateinit var expressAdapter: ExpressAdapter // private lateinit var financeAdapter: FinanceAdapter // private lateinit var financeAdapter: FinanceAdapter // private lateinit var memorialAdapter: MemorialAdapter private lateinit var dataUpdateReceiver: BroadcastReceiver //onCreateView这个方法创建后被调用,通常是初始化视图组件和观察者 override fun onCreateView( @@ -51,12 +56,26 @@ 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() { binding.expressRecycler.apply { layoutManager = LinearLayoutManager(context) expressAdapter = ExpressAdapter() adapter = expressAdapter // 设置点击监听 expressAdapter.setOnPackageClickListener { group, pack -> // 跳转到取件页面 @@ -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,22 +7,29 @@ android:orientation="vertical" android:padding="24dp"> <ImageButton android:id="@+id/btnBack" android:layout_width="48dp" android:layout_height="48dp" android:background="?attr/selectableItemBackgroundBorderless" android:src="@drawable/left_forward" android:padding="12dp" /> <TextView android:layout_width="wrap_content" <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="手机登录" android:textSize="24sp" android:textStyle="bold" android:textColor="#333333" /> android:gravity="center_vertical" android:orientation="horizontal"> <ImageButton android:id="@+id/btnBack" android:layout_width="48dp" android:layout_height="48dp" android:background="?attr/selectableItemBackgroundBorderless" android:src="@drawable/left_forward" android:padding="12dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" 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> <!-- <!– 关闭按钮 –>--> <!-- <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