first commit

This commit is contained in:
2026-04-25 17:22:36 +08:00
commit 8c691b0f68
62 changed files with 2290 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
package com.example.defenseapplication.login
import android.os.Bundle
import android.os.CountDownTimer
import android.text.InputType
import android.text.TextUtils
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.defenseapplication.R
import com.example.defenseapplication.databinding.ForgetPasswordActivityBinding
import com.example.defenseapplication.okhttp.OkHttpClientManager
import java.util.regex.Pattern
import kotlin.random.Random
class ForgetPasswordActivity : AppCompatActivity() {
private lateinit var binding: ForgetPasswordActivityBinding
private var verifyCode: String? = null // 存储模拟验证码
private var countDownTimer: CountDownTimer? = null
private var isSendingCode = false // 防止重复发送验证码
companion object {
private const val COUNTDOWN_DURATION = 60000L // 60秒倒计时
private const val COUNTDOWN_INTERVAL = 1000L
private const val MIN_PASSWORD_LENGTH = 6
private const val MAX_PASSWORD_LENGTH = 20
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ForgetPasswordActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
setupClickListeners()
}
private fun setupClickListeners() {
// 返回按钮
binding.backIcon.setOnClickListener {
finish()
}
// 发送验证码按钮
binding.sendCodeBtn.setOnClickListener {
if (!isSendingCode && countDownTimer == null) {
val phoneNumber = binding.etPhoneNumber.text.toString().trim()
if (isPhoneNumberValid(phoneNumber)) {
sendVerificationCode(phoneNumber)
} else {
Toast.makeText(this, "请输入有效的11位手机号", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "请稍后再试", Toast.LENGTH_SHORT).show()
}
}
// 确定按钮(重置密码)
binding.btnNext.setOnClickListener {
performResetPassword()
}
}
/**
* 手机号格式校验简单1开头的11位数字
*/
private fun isPhoneNumberValid(phone: String): Boolean {
if (TextUtils.isEmpty(phone)) return false
val pattern = Pattern.compile("^1[0-9]{10}$")
return pattern.matcher(phone).matches()
}
/**
* 发送验证码
*/
private fun sendVerificationCode(phoneNumber: String) {
isSendingCode = true
binding.sendCodeBtn.isEnabled = false
OkHttpClientManager.postForm(
url = "https://httpbin.org/post",
params = mapOf("phone" to phoneNumber)
) { result ->
result.onSuccess {
verifyCode = (100000 + Random.nextInt(900000)).toString()
startCountDown()
Toast.makeText(this, "验证码已发送(演示码:$verifyCode", Toast.LENGTH_SHORT).show()
}.onFailure {
Toast.makeText(this@ForgetPasswordActivity, "发送失败,请重试", Toast.LENGTH_SHORT).show()
resetSendButton()
}
isSendingCode = false
}
}
/**
* 开始验证码按钮倒计时
*/
private fun startCountDown() {
countDownTimer = object : CountDownTimer(COUNTDOWN_DURATION, COUNTDOWN_INTERVAL) {
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000).toInt()
binding.sendCodeBtn.text = "${secondsRemaining}秒后重试"
binding.sendCodeBtn.isEnabled = false
}
override fun onFinish() {
resetSendButton()
}
}.start()
}
/**
* 重置发送按钮状态
*/
private fun resetSendButton() {
countDownTimer?.cancel()
countDownTimer = null
binding.sendCodeBtn.text = "发送验证码"
binding.sendCodeBtn.isEnabled = true
}
/**
* 执行重置密码请求
*/
private fun performResetPassword() {
val phoneNumber = binding.etPhoneNumber.text.toString().trim()
val code = binding.etSmsCode.text.toString().trim()
val newPassword = binding.etPassword.text.toString().trim()
val confirmPassword = binding.etInviteCode.text.toString().trim()
// 1. 手机号校验
if (!isPhoneNumberValid(phoneNumber)) {
Toast.makeText(this, "手机号格式不正确", Toast.LENGTH_SHORT).show()
return
}
// 2. 验证码校验
if (TextUtils.isEmpty(code)) {
Toast.makeText(this, "请输入验证码", Toast.LENGTH_SHORT).show()
return
}
if (verifyCode == null) {
Toast.makeText(this, "请先获取验证码", Toast.LENGTH_SHORT).show()
return
}
if (code != verifyCode) {
Toast.makeText(this, "验证码错误", Toast.LENGTH_SHORT).show()
return
}
// 3. 密码校验
if (TextUtils.isEmpty(newPassword) || TextUtils.isEmpty(confirmPassword)) {
Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show()
return
}
if (newPassword.length < MIN_PASSWORD_LENGTH || newPassword.length > MAX_PASSWORD_LENGTH) {
Toast.makeText(this, "密码长度需为${MIN_PASSWORD_LENGTH}-${MAX_PASSWORD_LENGTH}", Toast.LENGTH_SHORT).show()
return
}
if (!newPassword.matches(Regex(".*[A-Za-z].*")) || !newPassword.matches(Regex(".*[0-9].*"))) {
Toast.makeText(this, "密码需同时包含字母和数字", Toast.LENGTH_SHORT).show()
return
}
if (newPassword != confirmPassword) {
Toast.makeText(this, "两次输入的密码不一致", Toast.LENGTH_SHORT).show()
return
}
// 禁用确定按钮,防重复提交
binding.btnNext.isEnabled = false
binding.btnNext.text = "重置中..."
val requestJson = """
{
"phone":"$phoneNumber",
"code":"$code",
"newPassword":"$newPassword"
}
""".trimIndent()
OkHttpClientManager.postJson(
url = "https://httpbin.org/post",
json = requestJson
) { result ->
result.onSuccess {
Toast.makeText(this, "密码重置成功,请重新登录", Toast.LENGTH_LONG).show()
finish()
}.onFailure {
Toast.makeText(this, "网络异常,请重试", Toast.LENGTH_SHORT).show()
}
binding.btnNext.isEnabled = true
binding.btnNext.text = "确定"
}
}
override fun onDestroy() {
super.onDestroy()
countDownTimer?.cancel()
countDownTimer = null
}
}

View File

@@ -0,0 +1,162 @@
package com.example.defenseapplication.login
import android.content.Intent
import com.example.defenseapplication.R
import android.os.Bundle
import android.os.CountDownTimer
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.example.defenseapplication.databinding.ActivityLoginBinding
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private var currentLoginMode = LoginMode.SMS
enum class LoginMode { SMS, PASSWORD }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
setupTabListeners()
setupClickListeners()
switchToSmsMode()
}
private fun setupTabListeners() {
binding.tvSmsLogin.setOnClickListener {
if (currentLoginMode != LoginMode.SMS) {
currentLoginMode = LoginMode.SMS
switchToSmsMode()
}
}
binding.tvPwdLogin.setOnClickListener {
if (currentLoginMode != LoginMode.PASSWORD) {
currentLoginMode = LoginMode.PASSWORD
switchToPasswordMode()
}
}
}
private fun switchToSmsMode() {
binding.tvSmsLogin.setTextColor(getColor(R.color.black))
binding.tvPwdLogin.setTextColor(getColor(R.color.gray))
binding.smsRowLayout.visibility = View.VISIBLE
binding.passwordRowLayout.visibility = View.GONE // 隐藏密码行容器
binding.registerText.visibility = View.VISIBLE
binding.forgetText.visibility = View.GONE
updateRegisterTextConstraints(center = true)
//需要正确的方法名和参数
updateLoginButtonTopConstraint(R.id.smsRowLayout)
}
private fun switchToPasswordMode() {
binding.tvPwdLogin.setTextColor(getColor(R.color.black))
binding.tvSmsLogin.setTextColor(getColor(R.color.gray))
binding.smsRowLayout.visibility = View.GONE
//正确的属性访问
binding.passwordRowLayout.visibility = View.VISIBLE // 显示密码行容器
binding.registerText.visibility = View.VISIBLE
binding.forgetText.visibility = View.VISIBLE
updateRegisterTextConstraints(center = false)
updateLoginButtonTopConstraint(R.id.passwordRowLayout)
}
// 动态修改登录按钮的顶部约束
private fun updateLoginButtonTopConstraint(topTargetId: Int) {
val params = binding.loginBtn.layoutParams as ConstraintLayout.LayoutParams
params.topToBottom = topTargetId
params.topMargin = 32
binding.loginBtn.layoutParams = params
}
// 动态修改立即注册的约束(居中/左对齐)
private fun updateRegisterTextConstraints(center: Boolean) {
val params = binding.registerText.layoutParams as ConstraintLayout.LayoutParams
if (center) {
// 短信模式:居中显示
params.startToStart = ConstraintSet.PARENT_ID
params.endToEnd = ConstraintSet.PARENT_ID
} else {
// 密码模式:左对齐登录按钮
params.startToStart = binding.loginBtn.id
params.endToEnd = ConstraintSet.UNSET
}
params.topToBottom = binding.loginBtn.id
params.topMargin = 20
binding.registerText.layoutParams = params
}
private fun setupClickListeners() {
binding.loginBtn.setOnClickListener {
val phone = binding.etPhone.text.toString()
if (phone.isEmpty()) {
Toast.makeText(this, "请输入手机号", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
when (currentLoginMode) {
LoginMode.SMS -> {
val code = binding.etSmsCode.text.toString()
if (code.isEmpty()) {
Toast.makeText(this, "请输入验证码", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "验证码登录成功\n手机号:$phone\n验证码:$code", Toast.LENGTH_SHORT).show()
}
}
LoginMode.PASSWORD -> {
val pwd = binding.etPassword.text.toString()
if (pwd.isEmpty()) {
Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "密码登录成功\n手机号:$phone\n密码:$pwd", Toast.LENGTH_SHORT).show()
}
}
}
}
binding.sendCodeBtn.setOnClickListener {
val phone = binding.etPhone.text.toString()
if (phone.isEmpty()) {
Toast.makeText(this, "请先输入手机号", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
Toast.makeText(this, "验证码已发送至 $phone", Toast.LENGTH_SHORT).show()
startCountDown()
}
binding.registerText.setOnClickListener {
val intent= Intent(this, RegisterActivity::class.java)
startActivity(intent)
Toast.makeText(this, "跳转到注册页面", Toast.LENGTH_SHORT).show()
}
binding.forgetText.setOnClickListener {
val intent= Intent(this, ForgetPasswordActivity::class.java)
startActivity(intent)
Toast.makeText(this, "找回密码", Toast.LENGTH_SHORT).show()
}
}
private fun startCountDown() {
binding.sendCodeBtn.isEnabled = false
object : CountDownTimer(60000, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.sendCodeBtn.text = "${millisUntilFinished / 1000}秒后重试"
}
override fun onFinish() {
binding.sendCodeBtn.text = "发送验证码"
binding.sendCodeBtn.isEnabled = true
}
}.start()
}
}

View File

@@ -0,0 +1,185 @@
package com.example.defenseapplication.login
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.defenseapplication.databinding.RegisterActivityBinding
/**
* 注册页面
* 功能:收集用户信息(手机号、身份证号、密码、邀请码),
* 进行前端格式校验,模拟注册请求并反馈结果。
*/
class RegisterActivity : AppCompatActivity() {
private lateinit var binding: RegisterActivityBinding
private var isLoading = false // 防止重复点击
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = RegisterActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
setupListeners()
//setupTextWatchers()
}
private fun setupListeners() {
// 返回按钮
binding.backIcon.setOnClickListener {
finish()
}
// 下一步按钮
binding.btnNext.setOnClickListener {
if (!isLoading) {
//performRegistration()
}
}
}
//
// /**
// * 为输入框添加文字监听,实时清除错误提示
// */
// private fun setupTextWatchers() {
// val errorClearWatcher = object : TextWatcher {
// override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
// override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
// override fun afterTextChanged(s: Editable?) {
// // 当用户开始输入时,清除该字段的错误
// when (binding.etPhoneNumber.hasFocus()) {
// true -> binding.etPhoneNumber.error = null
// else -> Unit
// }
// when (binding.etIdNumber.hasFocus()) {
// true -> binding.etIdNumber.error = null
// else -> Unit
// }
// when (binding.etPassword.hasFocus()) {
// true -> binding.etPassword.error = null
// else -> Unit
// }
// }
// }
//
// binding.etPhoneNumber.addTextChangedListener(errorClearWatcher)
// binding.etIdNumber.addTextChangedListener(errorClearWatcher)
// binding.etPassword.addTextChangedListener(errorClearWatcher)
// }
//
// /**
// * 执行注册流程:校验输入 -> 模拟网络请求
// */
// private fun performRegistration() {
// val phone = binding.etPhoneNumber.text.toString().trim()
// val idNumber = binding.etIdNumber.text.toString().trim()
// val password = binding.etPassword.text.toString().trim()
// val inviteCode = binding.etInviteCode.text.toString().trim()
//
// // 1. 校验
// val phoneValid = validatePhone(phone)
// val idValid = validateIdNumber(idNumber)
// val passwordValid = validatePassword(password)
//
// if (!phoneValid) {
// binding.etPhoneNumber.error = "请输入有效的11位手机号"
// binding.etPhoneNumber.requestFocus()
// return
// }
// if (!idValid) {
// binding.etIdNumber.error = "请输入正确的身份证号"
// binding.etIdNumber.requestFocus()
// return
// }
// if (!passwordValid) {
// binding.etPassword.error = "密码长度需为6~20位"
// binding.etPassword.requestFocus()
// return
// }
//
// // 2. 模拟注册请求(展示加载状态)
// isLoading = true
// binding.btnNext.isEnabled = false
// binding.btnNext.text = "注册中..."
//
// lifecycleScope.launch {
// // 模拟网络延迟 1.5 秒
// val registerResult = simulateRegister(phone, idNumber, password, inviteCode)
// delay(1500)
//
// isLoading = false
// binding.btnNext.isEnabled = true
// binding.btnNext.text = "下一步"
//
// if (registerResult) {
// Toast.makeText(this@RegisterActivity, "注册成功!", Toast.LENGTH_SHORT).show()
// // 实际项目中通常跳转到登录页或主页,这里简单关闭当前页面
// finish()
// } else {
// Toast.makeText(this@RegisterActivity, "注册失败,请稍后重试", Toast.LENGTH_SHORT).show()
// }
// }
// }
//
// /**
// * 模拟注册请求(实际应替换为 Retrofit/OkHttp 调用)
// */
// private suspend fun simulateRegister(
// phone: String,
// idNumber: String,
// password: String,
// inviteCode: String
// ): Boolean {
// // 模拟网络请求,此处始终返回成功(实际根据后端响应决定)
// // 您可以在这里添加真实网络请求逻辑
// return true
// }
// /**
// * 校验手机号中国大陆手机号简单校验1开头的11位数字第二位3-9
// */
// private fun validatePhone(phone: String): Boolean {
// val phoneRegex = Pattern.compile("^1[3-9]\\d{9}$")
// return phone.isNotEmpty() && phoneRegex.matcher(phone).matches()
// }
//
// /**
// * 校验身份证号支持15位和18位18位时校验最后一位校验码
// */
// private fun validateIdNumber(idNumber: String): Boolean {
// if (idNumber.isEmpty()) return false
// // 长度校验
// if (idNumber.length != 15 && idNumber.length != 18) return false
//
// // 15位全数字
// if (idNumber.length == 15) {
// return idNumber.all { it.isDigit() }
// }
//
// // 18位前17位为数字最后一位为数字或大写X
// val regex = Regex("^\\d{17}[\\dXx]$")
// if (!regex.matches(idNumber)) return false
//
// // 校验最后一位校验码(可选,提升准确性)
// return verifyIdCardChecksum(idNumber)
// }
//
// /**
// * 身份证最后一位校验码校验仅18位
// */
// private fun verifyIdCardChecksum(id18: String): Boolean {
// val weights = intArrayOf(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2)
// val checkCodes = charArrayOf('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2')
// var sum = 0
// for (i in 0 until 17) {
// sum += (id18[i] - '0') * weights[i]
// }
// val mod = sum % 11
// val expectedCheckCode = checkCodes[mod]
// return id18[17].uppercaseChar() == expectedCheckCode
// }
//
// /**
// * 校验密码长度6~20位
// */
// private fun validatePassword(password: String): Boolean {
// return password.length in 6..20
// }
}

View File

@@ -0,0 +1,4 @@
package com.example.defenseapplication.okhttp
class OkHttpClient {
}