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