tj
2025-03-28 dde2eeddbaf07246da7c2c352e7d49ec63fddcd2
Merge remote-tracking branch 'origin/master'

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