cloudroam
2025-02-21 2167ea58d1c297b0536d5cab6517707f1892b95f
登录;注册;关键字接口对接
已删除2个文件
已修改10个文件
已添加18个文件
1020 ■■■■ 文件已修改
.idea/vcs.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/AndroidManifest.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/App.kt 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/MainActivity.kt 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/LoginActivity.kt 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/MyAdapter2.kt 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/MyAdapter_bak.kt 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/core/Core.kt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/AppDatabase.kt 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/KeywordDao.kt 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/ApiResponse.kt 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/Code.kt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/KeywordConfig.kt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/KeywordEntity.kt 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/repository/KeywordRepository.kt 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ApiService.kt 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/login/LoginViewModel.kt 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/workers/KeywordUpdateWorker.kt 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/left_forward2.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/login_robot.png 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/activity_login.xml 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/activity_phone_login.xml 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/dialog_feedback.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_notifications.xml 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_layout.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/styles.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/xml/network_security_config.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/vcs.xml
对比新文件
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>
app/src/main/AndroidManifest.xml
@@ -116,7 +116,10 @@
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <!--    允许广播蓝牙设备信息。-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    <!--    允许语言识别。-->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 去掉主题   android:theme="@style/Theme.FirstApp"    android:theme="@style/Theme.AppCompat.Light.NoActionBar"-->
    <application
        android:name=".App"
        android:allowBackup="true"
@@ -127,9 +130,11 @@
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.FirstApp"
        android:networkSecurityConfig="@xml/network_security_config"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:name=".activity.LoginActivity"
            android:exported="true"
            android:label="@string/app_name">
            <intent-filter>
@@ -138,6 +143,10 @@
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity" />
        <activity
            android:name=".activity.PhoneLoginActivity"
            android:exported="false" />
    </application>
</manifest>
app/src/main/java/com/example/firstapp/App.kt
@@ -23,7 +23,9 @@
import com.hjq.language.OnLanguageListener
import com.example.firstapp.core.Core
import com.example.firstapp.database.repository.CodeRepository
import com.example.firstapp.database.repository.KeywordRepository
import com.example.firstapp.database.repository.MsgRepository
import com.example.firstapp.database.service.RetrofitClient
import com.example.firstapp.receiver.CactusReceiver
import com.example.firstapp.service.BluetoothScanService
import com.example.firstapp.service.HttpServerService
@@ -65,6 +67,7 @@
    val database by lazy { AppDatabase.getInstance(this) }
    val msgRepository by lazy { MsgRepository(database.msgDao()) }
    val codeRepository by lazy { CodeRepository(database.codeDao()) }
    val keywordRepository by lazy { KeywordRepository(RetrofitClient.apiService,database.keywordDao()) }
    companion object {
        const val TAG: String = "SmsForwarder"
app/src/main/java/com/example/firstapp/MainActivity.kt
@@ -18,13 +18,20 @@
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 androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
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.entity.Item
import com.example.firstapp.ui.home.HomeViewModel
import com.example.firstapp.utils.Log
import com.example.firstapp.workers.KeywordUpdateWorker
import java.util.Calendar
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
@@ -41,18 +48,23 @@
        if (isGranted) {
            // 权限授予后注册短信监听器
            registerSmsReceiver()
            syncRecentSms()
        } else {
            // 权限拒绝,提示用户
            Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setupViews()
//        binding.btnLogout.setOnClickListener {
//            logout()
//        }
        // 在此位置初始化 homeViewModel
        homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
@@ -73,27 +85,14 @@
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
            // 请求权限
            permissionRequest.launch(Manifest.permission.READ_SMS)
            syncRecentSms()
        } else {
            // 权限已经授予,继续执行相关操作
            registerSmsReceiver()
            syncRecentSms()
        }
        // 模拟数据
//        val items = listOf(
//            Item(1, "First Item", "This is the first item description"),
//            Item(2, "Second Item", "This is the second item description"),
//            Item(3, "Third Item", "This is the third item description")
//        )
//
//        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
//        recyclerView.layoutManager = LinearLayoutManager(this)
//        recyclerView.adapter = MyAdapter(items)
//        var codeList = Core.code.getAllDesc()
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
//        recyclerView.adapter = MyAdapter(codeList)
        // 初始化适配器
        adapter = MyAdapter()
@@ -124,8 +123,92 @@
    }
    private fun registerSmsReceiver() {
//        应用启动时执行 registerSmsReceiver()
//        创建 SmsReceiver 实例
//        注册广播接收器,开始监听短信
//        等待新短信到达
//        新短信到达时,系统发送广播
//        SmsReceiver 的 onReceive 方法被调用
//        处理短信内容
//        发送数据更新广播
//        MainActivity 接收到更新广播
//        更新 UI
        Log.d("SMS_DEBUG", "MainActivity收到数据更新广播")
        smsReceiver = SmsReceiver()
        val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)
        registerReceiver(smsReceiver, filter)
    }
    private fun setupKeywordUpdate() {
        val updateRequest = PeriodicWorkRequestBuilder<KeywordUpdateWorker>(
            1, TimeUnit.HOURS,  // 每小时更新一次
            15, TimeUnit.MINUTES  // 灵活时间窗口
        ).build()
        WorkManager.getInstance(this).enqueueUniquePeriodicWork(
            "keyword_update", ExistingPeriodicWorkPolicy.REPLACE, updateRequest
        )
    }
    private fun setupViews() {
        // 获取并显示当前登录的手机号
        val phone =
            getSharedPreferences("user_info", Context.MODE_PRIVATE).getString("phone", "") ?: ""
//        binding.apply {
//            tvPhone.text = "当前登录手机号:$phone"
//        }
    }
    private fun logout() {
        // 清除登录信息
        getSharedPreferences("user_info", Context.MODE_PRIVATE)
            .edit()
            .clear()
            .apply()
        // 跳转回登录页
        startActivity(Intent(this, LoginActivity::class.java))
        finish()
    }
    private fun syncRecentSms() {
        try {
            val calendar = Calendar.getInstance()
            calendar.add(Calendar.DAY_OF_YEAR, -3) // 获取3天前的时间
            val threeDaysAgo = calendar.timeInMillis
            val cursor = contentResolver.query(
                Uri.parse("content://sms/sent"),
                arrayOf("address", "body", "date"),
                "date >= ?",
                arrayOf(threeDaysAgo.toString()),
                "date DESC"
            )
            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 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)
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Toast.makeText(this, "同步短信失败:${e.message}", Toast.LENGTH_SHORT).show()
        }
    }
}
app/src/main/java/com/example/firstapp/activity/LoginActivity.kt
对比新文件
@@ -0,0 +1,44 @@
package com.example.firstapp.activity
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import android.content.Intent
import com.example.firstapp.databinding.ActivityLoginBinding
class LoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityLoginBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setupViews()
    }
    private fun setupViews() {
        binding.btnStartLogin.setOnClickListener {
            if (binding.cbAgreement.isChecked) {
                try {
                    val intent = Intent(this, PhoneLoginActivity::class.java)
                    startActivity(intent)
                    // 可以先不调用 finish(),确认跳转成功后再添加
                     finish()
                } catch (e: Exception) {
                    e.printStackTrace()
                    Toast.makeText(this, "跳转失败:${e.message}", Toast.LENGTH_SHORT).show()
                }
            } else {
                Toast.makeText(this, "请先同意用户协议和隐私政策", Toast.LENGTH_SHORT).show()
            }
        }
        binding.tvUserAgreement.setOnClickListener {
            // 打开用户协议
        }
        binding.tvPrivacyPolicy.setOnClickListener {
            // 打开隐私政策
        }
    }
}
app/src/main/java/com/example/firstapp/activity/PhoneLoginActivity.kt
对比新文件
@@ -0,0 +1,88 @@
package com.example.firstapp.activity
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.example.firstapp.MainActivity
import com.example.firstapp.databinding.ActivityPhoneLoginBinding
import com.example.firstapp.ui.login.LoginViewModel
import com.example.firstapp.utils.Log
class PhoneLoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityPhoneLoginBinding
    private val viewModel: LoginViewModel by viewModels()
    private var countDownTimer: CountDownTimer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("PhoneLoginActivity", "onCreate called")
        binding = ActivityPhoneLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setupViews()
        observeViewModel()
    }
    private fun setupViews() {
        binding.apply {
            btnBack.setOnClickListener {
                finish()
            }
            tvSendCode.setOnClickListener {
                val phone = etPhone.text.toString()
                if (phone.length == 11) {
                    viewModel.sendVerificationCode(phone)
                    startCountDown()
//                    17625318565 111111
                } else {
                    Toast.makeText(this@PhoneLoginActivity,
                        "请输入正确的手机号", Toast.LENGTH_SHORT).show()
                }
            }
            btnLogin.setOnClickListener {
                val phone = etPhone.text.toString()
                val code = etCode.text.toString()
                if (phone.length == 11 && code.length == 6) {
                    viewModel.login(phone, code)
                } else {
                    Toast.makeText(this@PhoneLoginActivity,
                        "请输入完整信息", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    private fun observeViewModel() {
        viewModel.loginState.observe(this) { isLoggedIn ->
            if (isLoggedIn) {
                startActivity(Intent(this, MainActivity::class.java))
                finishAffinity() // 结束所有之前的Activity
            }
        }
    }
    private fun startCountDown() {
        binding.tvSendCode.isEnabled = false
        countDownTimer?.cancel()
        countDownTimer = object : CountDownTimer(60000, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                binding.tvSendCode.text = "${millisUntilFinished / 1000}s"
            }
            override fun onFinish() {
                binding.tvSendCode.isEnabled = true
                binding.tvSendCode.text = "获取验证码"
            }
        }.start()
    }
    override fun onDestroy() {
        super.onDestroy()
        countDownTimer?.cancel()
    }
}
app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt
@@ -15,6 +15,7 @@
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val tvTitle: TextView = view.findViewById(R.id.tvTitle)
        val tvDescription: TextView = view.findViewById(R.id.tvDescription)
        val overTimeMsg: TextView = view.findViewById(R.id.overTimeMsg)
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -27,6 +28,9 @@
        val item = getItem(position) // 使用 getItem 来获取当前位置的 item
        holder.tvTitle.text = item.type // 假设 Code 类有一个 `type` 属性
        holder.tvDescription.text = item.code // 假设 Code 类有一个 `code` 属性
        var overtime  = "请注意:当前取件免费截止时间是"+item.overtime+",超时会收取额外费用"
        holder.overTimeMsg.text = overtime
        println("打印......")
    }
    // 使用 DiffUtil 来优化列表更新
app/src/main/java/com/example/firstapp/adapter/MyAdapter2.kt
文件已删除
app/src/main/java/com/example/firstapp/adapter/MyAdapter_bak.kt
文件已删除
app/src/main/java/com/example/firstapp/core/Core.kt
@@ -4,6 +4,7 @@
import androidx.work.Configuration
import com.example.firstapp.App
import com.example.firstapp.database.repository.CodeRepository
import com.example.firstapp.database.repository.KeywordRepository
import com.example.firstapp.database.repository.MsgRepository
import kotlinx.coroutines.launch
@@ -13,6 +14,7 @@
    val msg: MsgRepository by lazy { (app as App).msgRepository }
    val code: CodeRepository by lazy { (app as App).codeRepository }
    val keyword: KeywordRepository by lazy { (app as App).keywordRepository }
    fun init(app: Application) {
        this.app = app
app/src/main/java/com/example/firstapp/database/AppDatabase.kt
@@ -8,8 +8,10 @@
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.firstapp.database.dao.CodeDao
import com.example.firstapp.database.dao.KeywordDao
import com.example.firstapp.database.dao.MsgDao
import com.example.firstapp.database.entity.Code
import com.example.firstapp.database.entity.KeywordEntity
import com.example.firstapp.database.entity.Msg
import com.example.firstapp.utils.DATABASE_NAME
import com.example.firstapp.utils.SettingUtils
@@ -20,15 +22,16 @@
@Database(
    entities = [ Msg::class, Code::class],
    entities = [ Msg::class, Code::class, KeywordEntity::class],
//    views = [LogsDetail::class],
    version = 19,
    version = 20,
    exportSchema = false
)
@TypeConverters(ConvertersDate::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun msgDao(): MsgDao
    abstract fun codeDao(): CodeDao
    abstract fun keywordDao(): KeywordDao
    companion object {
        @Volatile
@@ -81,21 +84,31 @@
                //database.execSQL("Create table Msg as Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
                database.execSQL(
                    """
CREATE TABLE "Msg" (
  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  "type" TEXT NOT NULL DEFAULT 'sms',
  "from" TEXT NOT NULL DEFAULT '',
  "content" TEXT NOT NULL DEFAULT '',
  "sim_slot" INTEGER NOT NULL DEFAULT -1,
  "sim_info" TEXT NOT NULL DEFAULT '',
  "sub_id" INTEGER NOT NULL DEFAULT 0,
  "time" INTEGER NOT NULL
)
""".trimIndent()
                    CREATE TABLE "Msg" (
                      "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                      "type" TEXT NOT NULL DEFAULT 'sms',
                      "from" TEXT NOT NULL DEFAULT '',
                      "content" TEXT NOT NULL DEFAULT '',
                      "sim_slot" INTEGER NOT NULL DEFAULT -1,
                      "sim_info" TEXT NOT NULL DEFAULT '',
                      "sub_id" INTEGER NOT NULL DEFAULT 0,
                      "time" INTEGER NOT NULL
                    )
                """.trimIndent()
                )
                database.execSQL("CREATE UNIQUE INDEX \"index_Msg_id\" ON \"Msg\" ( \"id\" ASC)")
                // 新增 KeywordEntity 表的创建逻辑
                database.execSQL("""
                    CREATE TABLE IF NOT EXISTS `keywords` (
                        `id` INTEGER PRIMARY KEY AUTOINCREMENT,
                        `keyword` TEXT NOT NULL,
                        `type` TEXT NOT NULL,
                        `isEnabled` INTEGER NOT NULL
                    )
                """)
            }
        }
app/src/main/java/com/example/firstapp/database/dao/KeywordDao.kt
对比新文件
@@ -0,0 +1,27 @@
package com.example.firstapp.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.example.firstapp.database.entity.KeywordConfig
import com.example.firstapp.database.entity.KeywordEntity
@Dao
interface KeywordDao {
    @Query("SELECT * FROM keywords")
     fun getAllKeywords(): List<KeywordEntity>
//    @Insert(onConflict = OnConflictStrategy.REPLACE)
//    suspend fun insertAll(keywords: List<KeywordEntity>)
//
//    @Query("DELETE FROM keywords")
//    suspend fun deleteAll()
//
//    @Update
//    suspend fun update(keyword: KeywordEntity)
//
//    @Query("SELECT * FROM keywords WHERE isEnabled = 1")
//     fun getEnabledKeywords(): List<KeywordConfig>
}
app/src/main/java/com/example/firstapp/database/entity/ApiResponse.kt
对比新文件
@@ -0,0 +1,8 @@
package com.example.firstapp.database.entity
data class ApiResponse<T>(
    val status: Int,
    val msg: String,
    val info: String,
    val data: T
)
app/src/main/java/com/example/firstapp/database/entity/Code.kt
@@ -14,5 +14,6 @@
    val ruleId: Long,
    val msgId: Long,
    val code: String,
    var overtime: String,
    var time: Date = Date(),
)
app/src/main/java/com/example/firstapp/database/entity/KeywordConfig.kt
对比新文件
@@ -0,0 +1,11 @@
package com.example.firstapp.database.entity
/**
 *   关键字策略匹配类
 */
data class KeywordConfig(
    val id: Long = 0, // 自增长的 id
    val type: String,
    val keyword: String,
    val isEnable: Boolean
)
app/src/main/java/com/example/firstapp/database/entity/KeywordEntity.kt
对比新文件
@@ -0,0 +1,13 @@
package com.example.firstapp.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.*
@Entity(tableName = "keywords")
data class KeywordEntity(
    @PrimaryKey(autoGenerate = true) val id: Long = 0, // 自增长的 id
    val keyword: String,
    val type: String,
    val isEnabled: Boolean
)
app/src/main/java/com/example/firstapp/database/repository/KeywordRepository.kt
对比新文件
@@ -0,0 +1,40 @@
package com.example.firstapp.database.repository
import com.example.firstapp.database.dao.KeywordDao
import com.example.firstapp.database.entity.KeywordConfig
import com.example.firstapp.database.entity.KeywordEntity
import com.example.firstapp.database.service.ApiService
class KeywordRepository(
    private val apiService: ApiService,
    //本地缓存
    private val keywordDao: KeywordDao
) {
    suspend fun getKeywords(): List<KeywordEntity> {
        return try {
            // 从网络获取配置
            val response = apiService.getKeywords()
            if (response.status == 1) {
                // 保存到本地数据库作为缓存
                saveToLocal(response.data)
                response.data
            } else {
                // 如果接口请求失败,使用本地缓存
                keywordDao.getAllKeywords()
            }
        } catch (e: Exception) {
            e.printStackTrace()  // 打印完整堆栈信息 [[3,5]]
            // 或使用以下方式输出基本信息
            println("网络请求异常: ${e.message}")  // 打印异常消息 [[5]]
            // 网络错误时使用本地缓存
            keywordDao.getAllKeywords()
        }
    }
    private suspend fun saveToLocal(keywords: List<KeywordEntity>) {
        true
        //keywordDao.insertAll(keywords.map { it.toEntity() })
    }
}
app/src/main/java/com/example/firstapp/database/service/ApiService.kt
对比新文件
@@ -0,0 +1,30 @@
package com.example.firstapp.database.service
import com.example.firstapp.database.entity.ApiResponse
import com.example.firstapp.database.entity.KeywordConfig
import com.example.firstapp.database.entity.KeywordEntity
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
/**
 * API调用接口
 */
interface ApiService {
    @GET("keywords")
    suspend fun getKeywords():ApiResponse<List<KeywordEntity>>  //异步挂起
}
// 创建Retrofit实例(单例)
object RetrofitClient{
    private const val BASE_URL ="http://47.96.225.205:9009/cloud/"
    //添加Gson解析器,用于自动将JSON响应转换为Kotlin/Java对象
    private val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()
    //通过动态代理技术创建ApiService接口的具体实现类
    val apiService:ApiService = retrofit.create(ApiService::class.java)
}
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
@@ -3,20 +3,30 @@
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Telephony
import android.telephony.SmsMessage
import android.util.Log
import androidx.annotation.RequiresApi
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.entity.Rule
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class SmsReceiver : BroadcastReceiver() {
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onReceive(context: Context, intent: Intent) {
        // 检查广播的 Action 是否为短信接收
        if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION == intent.action) {
            // 获取短信内容
@@ -43,18 +53,42 @@
                    Rule("快递","菜鸟驿站","\\d{1,2}-\\d{1,2}-\\d{4}")
                )
                CoroutineScope(Dispatchers.IO).launch {
                    Log.d("SmsReceiver", "CoroutineScope started")
                    // 获取最新的关键词配置
                    val keywords = Core.keyword.getKeywords()
                    Log.d("keywords", keywords.toString())
                    println(keywords)
                    // 保存匹配的短信
                    //saveMessage(content)
                }
                // 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)
                        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/login/LoginViewModel.kt
对比新文件
@@ -0,0 +1,45 @@
package com.example.firstapp.ui.login
import android.app.Application
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import androidx.lifecycle.AndroidViewModel
class LoginViewModel(application: Application) : AndroidViewModel(application) {
    private val _loginState = MutableLiveData<Boolean>()
    val loginState: LiveData<Boolean> = _loginState
    fun sendVerificationCode(phone: String) {
        viewModelScope.launch {
            // 这里实现发送验证码的逻辑
            // 模拟网络请求
            delay(1000)
            // 实际应用中需要调用后端API
        }
    }
    fun login(phone: String, code: String) {
        viewModelScope.launch {
            // 模拟登录请求
            delay(1000)
            // 保存登录状态和手机号
            saveLoginInfo(phone)
            _loginState.value = true
        }
    }
    private fun saveLoginInfo(phone: String) {
        getApplication<Application>().getSharedPreferences(
            "user_info", Context.MODE_PRIVATE
        ).edit().apply {
            putBoolean("is_logged_in", true)
            putString("phone", phone)
            apply()
        }
    }
}
app/src/main/java/com/example/firstapp/workers/KeywordUpdateWorker.kt
对比新文件
@@ -0,0 +1,31 @@
package com.example.firstapp.workers
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.firstapp.database.dao.KeywordDao
import com.example.firstapp.database.repository.KeywordRepository
import com.example.firstapp.database.service.RetrofitClient
class KeywordUpdateWorker(
    context: Context,
    params: WorkerParameters,
    private val keywordDao: KeywordDao // 注入 KeywordDao
) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
// 另外一种实例化的方式
//        val database = AppDatabase.getInstance(applicationContext) // 获取数据库实例
//        val keywordDao = database.keywordDao() // 获取 KeywordDao 实例
//        val repository = KeywordRepository(RetrofitClient.apiService, keywordDao) // 传递 keywordDao
        val repository = KeywordRepository(RetrofitClient.apiService,keywordDao)
        return try {
            // 更新配置
            repository.getKeywords()
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}
app/src/main/res/drawable/left_forward2.png
app/src/main/res/drawable/login_robot.png
app/src/main/res/layout/activity_login.xml
对比新文件
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="欢迎来到"
        android:textSize="24sp"
        android:textColor="#333333" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="智信管家!"
        android:textSize="28sp"
        android:textStyle="bold"
        android:textColor="#333333" />
    <ImageView
        android:id="@+id/ivLogo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="24dp"
        android:layout_weight="0"
        android:scaleType="centerCrop"
        android:src="@drawable/login_robot" />
    <Button
        android:id="@+id/btnStartLogin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="手机号登录/注册"
        android:textColor="#FFFFFF"
        android:textSize="16sp"
        android:backgroundTint="@color/cardview_dark_background"
        app:cornerRadius="14dp" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="16dp"
        android:orientation="horizontal">
        <CheckBox
            android:id="@+id/cbAgreement"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我已阅读并同意"
            android:textSize="14sp" />
        <TextView
            android:id="@+id/tvUserAgreement"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="用户协议"
            android:textColor="#2196F3"
            android:textSize="14sp" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=" &amp; "
            android:textSize="14sp" />
        <TextView
            android:id="@+id/tvPrivacyPolicy"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="隐私政策"
            android:textColor="#2196F3"
            android:textSize="14sp" />
    </LinearLayout>
</LinearLayout>
app/src/main/res/layout/activity_phone_login.xml
对比新文件
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    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_forward2"
        android:padding="12dp" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="手机登录"
        android:textSize="24sp"
        android:textStyle="bold"
        android:textColor="#333333" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:orientation="vertical">
        <EditText
            android:id="@+id/etPhone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@null"
            android:hint="请输入手机号"
            android:text="177625318565"
            android:inputType="phone"
            android:maxLength="11"
            android:textSize="16sp"
            android:padding="12dp"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#EEEEEE" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginTop="16dp">
            <EditText
                android:id="@+id/etCode"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:background="@null"
                android:hint="请输入验证码"
                android:text="123456"
                android:inputType="number"
                android:maxLength="6"
                android:textSize="16sp"
                android:padding="12dp"/>
            <TextView
                android:id="@+id/tvSendCode"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="12dp"
                android:text="获取验证码"
                android:textColor="#2196F3"/>
        </LinearLayout>
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#EEEEEE" />
    </LinearLayout>
    <Button
        android:id="@+id/btnLogin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:backgroundTint="@color/cardview_dark_background"
        android:text="登录"
        android:textColor="#FFFFFF"
        android:textSize="16sp"
        android:padding="12dp"
        app:cornerRadius="14dp"/>
</LinearLayout>
app/src/main/res/layout/dialog_feedback.xml
对比新文件
@@ -0,0 +1 @@
app/src/main/res/layout/fragment_notifications.xml
@@ -1,22 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.notifications.NotificationsFragment">
    android:orientation="vertical">
    <!-- 标题栏 -->
    <TextView
        android:id="@+id/text_notifications"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:textAlignment="center"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
        android:background="#FFE4C4"
        android:gravity="center"
        android:padding="16dp"
        android:text="终身会员"
        android:textSize="18sp" />
    <!-- 功能区标题 -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="功能"
        android:textSize="14sp" />
    <!-- 设置选项 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <!-- 设置提醒 -->
        <RelativeLayout
            android:id="@+id/settings_reminder"
            style="@style/SettingsItem">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:text="设置提醒" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:src="@android:drawable/ic_menu_more" />
        </RelativeLayout>
        <!-- 取录与反馈 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="取录与反馈"
            android:textSize="14sp" />
        <!-- 关于小红书 -->
        <RelativeLayout
            android:id="@+id/about_app"
            style="@style/SettingsItem">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:text="关于小红书" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:src="@android:drawable/ic_menu_more" />
        </RelativeLayout>
        <!-- 邮件联系 -->
        <RelativeLayout
            android:id="@+id/email_contact"
            style="@style/SettingsItem">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:text="邮件联系" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:src="@android:drawable/ic_menu_more" />
        </RelativeLayout>
        <!-- 意见与反馈 -->
        <RelativeLayout
            android:id="@+id/feedback"
            style="@style/SettingsItem">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:text="意见与反馈" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:src="@android:drawable/ic_menu_more" />
        </RelativeLayout>
        <!-- 分享给好友 -->
        <RelativeLayout
            android:id="@+id/share_to_friends"
            style="@style/SettingsItem">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:text="分享给好友" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:src="@android:drawable/ic_menu_more" />
        </RelativeLayout>
        <!-- 其他区域标题 -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="其他"
            android:textSize="14sp" />
        <!-- 隐私协议 -->
        <RelativeLayout
            android:id="@+id/privacy_policy"
            style="@style/SettingsItem">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:text="隐私协议" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:src="@android:drawable/ic_menu_more" />
        </RelativeLayout>
        <!-- 如何使用 -->
        <RelativeLayout
            android:id="@+id/how_to_use"
            style="@style/SettingsItem">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:text="如何使用" />
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:src="@android:drawable/ic_menu_more" />
        </RelativeLayout>
    </LinearLayout>
</LinearLayout>
app/src/main/res/layout/item_layout.xml
@@ -6,6 +6,8 @@
    android:orientation="vertical"
    android:padding="16dp">
    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
@@ -19,4 +21,11 @@
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:layout_marginTop="4dp"/>
    <TextView
        android:id="@+id/overTimeMsg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:textSize="14sp" />
</LinearLayout>
app/src/main/res/values/styles.xml
对比新文件
@@ -0,0 +1,8 @@
<style name="SettingsItem">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:padding">16dp</item>
    <item name="android:background">?android:attr/selectableItemBackground</item>
    <item name="android:clickable">true</item>
    <item name="android:focusable">true</item>
</style>
app/src/main/res/xml/network_security_config.xml
对比新文件
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">47.96.225.205</domain>
        <!-- 可添加其他域名或IP(如192.168.0.101) -->
    </domain-config>
</network-security-config>