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