cloudroam
2025-02-25 0fce8fea0b83afb02b5d8780160787e87b8ceedb
新增
已修改11个文件
已添加6个文件
511 ■■■■ 文件已修改
app/src/main/java/com/example/firstapp/App.kt 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/ReminderAdapter.kt 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/core/Core.kt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/AppDatabase.kt 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/KeywordDao.kt 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/ReminderDao.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | 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 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/Reminder.kt 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/repository/KeywordRepository.kt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/repository/ReminderRepository.kt 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ApiService.kt 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/reminder/ReminderSettingsFragment.kt 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/reminder/ReminderViewModel.kt 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_reminder_settings.xml 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_reminder.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/App.kt
@@ -3,14 +3,10 @@
import android.annotation.SuppressLint
import android.app.Application
import android.app.PendingIntent
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.location.Geocoder
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import android.os.Build
import androidx.lifecycle.MutableLiveData
import androidx.multidex.MultiDex
@@ -20,34 +16,26 @@
import com.gyf.cactus.callback.CactusCallback
import com.gyf.cactus.ext.cactus
import com.hjq.language.MultiLanguages
import com.hjq.language.OnLanguageListener
import com.example.firstapp.core.Core
import com.example.firstapp.database.repository.ReminderRepository
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
import com.example.firstapp.utils.ACTION_START
import com.example.firstapp.utils.AppInfo
import com.example.firstapp.utils.CactusSave
import com.example.firstapp.utils.FRONT_CHANNEL_ID
import com.example.firstapp.utils.FRONT_CHANNEL_NAME
import com.example.firstapp.utils.FRONT_NOTIFY_ID
import com.example.firstapp.utils.FRPC_LIB_VERSION
import com.example.firstapp.utils.HistoryUtils
import com.example.firstapp.utils.Log
import com.example.firstapp.utils.SettingUtils
import com.example.firstapp.utils.SharedPreference
import com.example.firstapp.utils.tinker.TinkerLoadLibrary
import com.king.location.LocationClient
import com.xuexiang.xutil.file.FileUtils
import frpclib.Frpclib
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import java.io.BufferedWriter
@@ -57,8 +45,6 @@
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
@Suppress("DEPRECATION")
class App : Application(), CactusCallback, Configuration.Provider by Core {
@@ -67,8 +53,10 @@
    val database by lazy { AppDatabase.getInstance(this) }
    val msgRepository by lazy { MsgRepository(database.msgDao()) }
    val codeRepository by lazy { CodeRepository(database.codeDao()) }
    val reminderRepository by lazy { ReminderRepository(database.reminderDao()) }
    val keywordRepository by lazy { KeywordRepository(RetrofitClient.apiService,database.keywordDao()) }
    companion object {
        const val TAG: String = "SmsForwarder"
app/src/main/java/com/example/firstapp/adapter/ReminderAdapter.kt
对比新文件
@@ -0,0 +1,39 @@
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.firstapp.database.entity.Reminder
import com.example.firstapp.databinding.ItemReminderBinding
import com.sun.mail.imap.protocol.FetchResponse.getItem
class ReminderAdapter(private val onDelete: (Reminder) -> Unit) :
    ListAdapter<Reminder, ReminderAdapter.ReminderViewHolder>(ReminderDiffCallback()) {
    class ReminderViewHolder(private val binding: ItemReminderBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(reminder: Reminder, onDelete: (Reminder) -> Unit) {
            binding.textNickname.text = reminder.nickname
            binding.textKeywords.text = reminder.keywords
            binding.btnDelete.setOnClickListener { onDelete(reminder) }
        }
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReminderViewHolder {
        return ReminderViewHolder(
            ItemReminderBinding.inflate(
                LayoutInflater.from(parent.context), parent, false
            )
        )
    }
    override fun onBindViewHolder(holder: ReminderViewHolder, position: Int) {
        holder.bind(getItem(position), onDelete)
    }
}
class ReminderDiffCallback : DiffUtil.ItemCallback<Reminder>() {
    override fun areItemsTheSame(oldItem: Reminder, newItem: Reminder) = oldItem.id == newItem.id
    override fun areContentsTheSame(oldItem: Reminder, newItem: Reminder) = oldItem == newItem
}
app/src/main/java/com/example/firstapp/core/Core.kt
@@ -6,6 +6,7 @@
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.repository.ReminderRepository
import kotlinx.coroutines.launch
@@ -15,6 +16,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 }
    val reminder: ReminderRepository by lazy { (app as App).reminderRepository }
    fun init(app: Application) {
        this.app = app
app/src/main/java/com/example/firstapp/database/AppDatabase.kt
@@ -1,5 +1,6 @@
package com.example.firstapp
import android.content.Context
import androidx.room.Database
import androidx.room.Room
@@ -10,9 +11,11 @@
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.dao.ReminderDao
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.database.entity.Reminder
import com.example.firstapp.utils.DATABASE_NAME
import com.example.firstapp.utils.SettingUtils
import com.example.firstapp.utils.TAG_LIST
@@ -22,8 +25,7 @@
@Database(
    entities = [ Msg::class, Code::class, KeywordEntity::class],
//    views = [LogsDetail::class],
    entities = [ Msg::class, Code::class, KeywordEntity::class, Reminder::class],
    version = 20,
    exportSchema = false
)
@@ -32,6 +34,7 @@
    abstract fun msgDao(): MsgDao
    abstract fun codeDao(): CodeDao
    abstract fun keywordDao(): KeywordDao
    abstract fun reminderDao(): ReminderDao
    companion object {
        @Volatile
@@ -108,6 +111,14 @@
                        `isEnabled` INTEGER NOT NULL
                    )
                """)
//                database.execSQL("""
//                   CREATE TABLE   IF NOT EXISTS `reminders` (
//                    id INTEGER PRIMARY KEY AUTOINCREMENT,
//                    type TEXT NOT NULL,
//                    nickname TEXT NOT NULL,
//                    keywords TEXT NOT NULL,
//                );
//                """)
            }
        }
app/src/main/java/com/example/firstapp/database/dao/KeywordDao.kt
@@ -8,20 +8,30 @@
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>
//@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>
}
////
////    @Query("DELETE FROM keywords")
////    suspend fun deleteAll()
////
////    @Update
////    suspend fun update(keyword: KeywordEntity)
////
////    @Query("SELECT * FROM keywords WHERE isEnabled = 1")
////     fun getEnabledKeywords(): List<KeywordConfig>
//}
@Dao
interface KeywordDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
     fun insertAll(keywords: List<KeywordEntity>)
    @Query("SELECT * FROM keywords")
     fun getAllKeywords(): List<KeywordEntity>
}
app/src/main/java/com/example/firstapp/database/dao/ReminderDao.kt
对比新文件
@@ -0,0 +1,17 @@
package com.example.firstapp.database.dao
import androidx.room.*
import com.example.firstapp.database.entity.Reminder
import kotlinx.coroutines.flow.Flow
@Dao
interface ReminderDao {
    @Query("SELECT * FROM reminders ORDER BY type")
    fun getAllReminders(): Flow<List<Reminder>>
    @Insert
     fun insert(reminder: Reminder)
    @Delete
     fun delete(reminder: Reminder)
}
app/src/main/java/com/example/firstapp/database/entity/KeywordConfig.kt
@@ -8,4 +8,13 @@
    val type: String,
    val keyword: String,
    val isEnable: Boolean
)
){
    fun toEntity(): KeywordEntity {
        return KeywordEntity(
            id = id,          // 确保与@Entity类字段匹配
            type = type,
            keyword = keyword,
            isEnabled = isEnable  // 注意字段名是否一致(isEnabled vs isEnable)
        )
    }
}
app/src/main/java/com/example/firstapp/database/entity/KeywordEntity.kt
@@ -10,4 +10,4 @@
    val keyword: String,
    val type: String,
    val isEnabled: Boolean
)
)
app/src/main/java/com/example/firstapp/database/entity/Reminder.kt
对比新文件
@@ -0,0 +1,13 @@
package com.example.firstapp.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "reminders")
data class Reminder(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val type: String,
    val nickname: String,
    val keywords: String
)
app/src/main/java/com/example/firstapp/database/repository/KeywordRepository.kt
@@ -18,7 +18,7 @@
            if (response.status == 1) {
                // 保存到本地数据库作为缓存
                saveToLocal(response.data)
                response.data
                keywordDao.getAllKeywords()
            } else {
                // 如果接口请求失败,使用本地缓存
                keywordDao.getAllKeywords()
@@ -32,9 +32,12 @@
        }
    }
    private suspend fun saveToLocal(keywords: List<KeywordEntity>) {
        true
        //keywordDao.insertAll(keywords.map { it.toEntity() })
    private suspend fun saveToLocal(keywords: List<KeywordConfig>) {
//        keywords.map { it.toEntity() }
//        keywordDao.insertAll(keywords)
        val keywordEntities = keywords.map { it.toEntity() }
        keywordDao.insertAll(keywordEntities)
    }
}
app/src/main/java/com/example/firstapp/database/repository/ReminderRepository.kt
对比新文件
@@ -0,0 +1,21 @@
package com.example.firstapp.database.repository
import com.example.firstapp.database.dao.ReminderDao
import androidx.annotation.WorkerThread
import com.example.firstapp.database.entity.Reminder
import kotlinx.coroutines.flow.Flow
class ReminderRepository(private val reminderDao: ReminderDao) {
    val allReminders: Flow<List<Reminder>> = reminderDao.getAllReminders()
    @WorkerThread
     fun insert(reminder: Reminder) {
        reminderDao.insert(reminder)
    }
    @WorkerThread
     fun delete(reminder: Reminder) {
        reminderDao.delete(reminder)
    }
}
app/src/main/java/com/example/firstapp/database/service/ApiService.kt
@@ -13,7 +13,7 @@
interface ApiService {
    @GET("keywords")
    suspend fun getKeywords():ApiResponse<List<KeywordEntity>>  //异步挂起
    suspend fun getKeywords():ApiResponse<List<KeywordConfig>>  //异步挂起
}
// 创建Retrofit实例(单例)
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
@@ -48,7 +48,7 @@
                Log.d("SmsReceiver", "Received SMS msgId: ${msgId}")
                // 这里我要写个数组,并创建个对象存放一些内容,如这个对象的属性有匹配内容,正则表达式,并循环遍历
                val ruleList = listOf(
                val ruleList = mutableListOf(
                    Rule("快递","京东","\\d{6}"),
                    Rule("快递","菜鸟驿站","\\d{1,2}-\\d{1,2}-\\d{4}")
                )
@@ -58,41 +58,76 @@
                    // 获取最新的关键词配置
                    val keywords = Core.keyword.getKeywords()
                    Log.d("keywords", keywords.toString())
                    println(keywords)
                    // 保存匹配的短信
                    //saveMessage(content)
                    keywords.forEach { keyword ->
                        ruleList.add(
                            Rule(
                                keyword.type,
                                keyword.keyword,
                              "\\d{1,2}-\\d{1,2}-\\d{4}"
                            )
                        )
                    }
                    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}")
                            // 获取当前时间
                            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: 没有匹配到内容")
                        }
                    }
                }
                // 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: 没有匹配到内容")
                    }
                }
//                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/reminder/ReminderSettingsFragment.kt
@@ -1,18 +1,30 @@
package com.example.firstapp.ui.reminder
import ReminderAdapter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.firstapp.App
import com.example.firstapp.database.entity.Reminder
import com.example.firstapp.databinding.FragmentReminderSettingsBinding
class ReminderSettingsFragment : Fragment() {
    private var _binding: FragmentReminderSettingsBinding? = null
    private val binding get() = _binding!!
    private val reminderViewModel: ReminderViewModel by viewModels {
        ReminderViewModelFactory((requireActivity().application as App).reminderRepository)
    }
    private lateinit var adapter: ReminderAdapter
    override fun onCreateView(
        inflater: LayoutInflater,
@@ -26,21 +38,60 @@
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 返回按钮点击事件
        binding.btnBack.setOnClickListener {
        setupSpinner()
        setupRecyclerView()
        setupClickListeners()
        observeReminders()
    }
    private fun setupSpinner() {
        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
    }
    private fun setupRecyclerView() {
        adapter = ReminderAdapter { reminder ->
            reminderViewModel.deleteReminder(reminder)
        }
        binding.recyclerReminders.apply {
            layoutManager = LinearLayoutManager(context)
            this.adapter = this@ReminderSettingsFragment.adapter
        }
    }
    private fun setupClickListeners() {
        binding.btnClose.setOnClickListener {
            findNavController().navigateUp()
        }
        // 添加提醒按钮点击事件
        binding.btnAddReminder.setOnClickListener {
            val reminderText = binding.editQuickReminder.text.toString()
            if (reminderText.isNotEmpty()) {
                // TODO: 保存提醒到数据库
                Toast.makeText(context, "提醒已添加", Toast.LENGTH_SHORT).show()
                binding.editQuickReminder.text.clear()
            } else {
                Toast.makeText(context, "请输入提醒内容", Toast.LENGTH_SHORT).show()
            val type = binding.spinnerType.selectedItem.toString()
            val nickname = binding.editNickname.text.toString()
            val keywords = binding.editKeywords.text.toString()
            if (nickname.isBlank() || keywords.isBlank()) {
                Toast.makeText(context, "请填写完整信息", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            reminderViewModel.insertReminder(
                Reminder(
                    type = type,
                    nickname = nickname,
                    keywords = keywords
                )
            )
            binding.editNickname.text.clear()
            binding.editKeywords.text.clear()
        }
    }
    private fun observeReminders() {
        reminderViewModel.allReminders.observe(viewLifecycleOwner) { reminders ->
            adapter.submitList(reminders)
        }
    }
app/src/main/java/com/example/firstapp/ui/reminder/ReminderViewModel.kt
对比新文件
@@ -0,0 +1,33 @@
package com.example.firstapp.ui.reminder
import androidx.lifecycle.*
import com.example.firstapp.database.entity.Reminder
import com.example.firstapp.database.repository.ReminderRepository
import kotlinx.coroutines.launch
class ReminderViewModel(private val repository: ReminderRepository) : ViewModel() {
    // 使用 Flow 获取所有提醒
    val allReminders: LiveData<List<Reminder>> = repository.allReminders.asLiveData()
    // 插入新提醒
    fun insertReminder(reminder: Reminder) = viewModelScope.launch {
        repository.insert(reminder)
    }
    // 删除提醒
    fun deleteReminder(reminder: Reminder) = viewModelScope.launch {
        repository.delete(reminder)
    }
}
// ViewModel Factory
class ReminderViewModelFactory(private val repository: ReminderRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(ReminderViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return ReminderViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
app/src/main/res/layout/fragment_reminder_settings.xml
@@ -8,47 +8,81 @@
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#FFFFFF"
        android:elevation="4dp">
        <!-- 返回按钮 -->
        <ImageButton
            android:id="@+id/btn_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerVertical="true"
            android:background="?attr/selectableItemBackgroundBorderless"
            android:src="@android:drawable/ic_menu_close_clear_cancel"
            android:contentDescription="返回" />
        android:background="#FFFFFF">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="标题"
            android:text="新增短信提醒"
            android:textSize="18sp"
            android:textColor="#000000" />
        <!-- 关闭按钮 -->
        <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:src="@android:drawable/ic_menu_close_clear_cancel" />
    </RelativeLayout>
    <!-- 快速提醒输入框 -->
    <EditText
        android:id="@+id/edit_quick_reminder"
    <!-- 提醒类型选择 -->
    <Spinner
        android:id="@+id/spinner_type"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:background="@android:drawable/edit_text"
        android:hint="快速提醒"
        android:padding="12dp" />
        android:padding="12dp"
        android:background="@android:drawable/btn_dropdown" />
    <!-- 添加提醒按钮 -->
    <!-- 昵称输入框 -->
    <EditText
        android:id="@+id/edit_nickname"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:hint="昵称"
        android:padding="12dp"
        android:background="@android:drawable/edit_text" />
    <!-- 关键词输入框 -->
    <EditText
        android:id="@+id/edit_keywords"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:hint="关键词"
        android:padding="12dp"
        android:background="@android:drawable/edit_text" />
    <!-- 已添加列表标题 -->
    <Button
        android:id="@+id/btn_add_reminder"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="16dp"
        android:layout_marginTop="16dp"
        android:layout_margin="16dp"
        android:backgroundTint="#03A9F4"
        android:text="添加到提醒"
        android:text="确定新增"
        android:textColor="#FFFFFF" />
</LinearLayout>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="已添加列表"
        android:textStyle="bold" />
    <!-- 已添加列表 -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_reminders"
        android:layout_width="match_parent"
        android:layout_height="221dp"
        android:layout_weight="1"
        android:padding="8dp" />
    <!-- 确定新增按钮 -->
</LinearLayout>
app/src/main/res/layout/item_reminder.xml
对比新文件
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="12dp"
    android:background="@android:color/white">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toStartOf="@id/btn_delete"
        android:orientation="vertical">
        <TextView
            android:id="@+id/text_nickname"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textColor="#000000" />
        <TextView
            android:id="@+id/text_keywords"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:textSize="14sp"
            android:textColor="#666666" />
    </LinearLayout>
    <ImageButton
        android:id="@+id/btn_delete"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_alignParentEnd="true"
        android:layout_centerVertical="true"
        android:background="@null"
        android:src="@android:drawable/ic_delete" />
</RelativeLayout>