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