修改全局按钮背景阴影,生成二维码和撤销接口调用

This commit is contained in:
2026-06-20 16:11:09 +08:00
parent 6a1cfaeda9
commit b55d9a4b38
22 changed files with 251 additions and 68 deletions

View File

@@ -1,28 +1,39 @@
package com.ckkj.defense_device.personal
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomsheet.BottomSheetDialog
import androidx.core.content.ContextCompat
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.RecommendationCodeBean
import com.ckkj.defense_device.bean.VerifyOptionPasswordRequest
import com.ckkj.defense_device.databinding.DialogExpireTimeBinding
import com.ckkj.defense_device.databinding.InvitationCodeActivityBinding
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.google.gson.Gson
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.gyf.immersionbar.ImmersionBar
import com.maning.mlkitscanner.scan.MNScanManager
import com.maning.mlkitscanner.scan.callback.act.MNScanCallback
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -44,7 +55,24 @@ class InvitationCodeActivity : AppCompatActivity() {
private var isLoading = false // 是否正在加载
private var hasGeneratedQrCode = false // 是否已经生成过二维码
private val requestPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] ?: false
val storageGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions[Manifest.permission.READ_MEDIA_IMAGES] ?: false
} else {
permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false
}
if (cameraGranted && storageGranted) {
startScan()
} else {
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_SHORT).show()
}
}
companion object {
private const val TAG = "InvitationCodeActivity"
const val EXPIRE_24H = 24 * 60 * 60 * 1000L
const val EXPIRE_7D = 7 * 24 * 60 * 60 * 1000L
}
@@ -90,30 +118,130 @@ class InvitationCodeActivity : AppCompatActivity() {
// 撤销邀请按钮
binding.btnCancelInvite.setOnClickListener {
showPasswordDialog {
cancelInvitation()
if (generatedInviteCode.isEmpty()) {
ToastUtils.showError(this, getString(R.string.qr_code_expired))
return@setOnClickListener
}
showPasswordDialog { password, dialog ->
cancelInvitation(password, dialog)
}
}
// 生成/重新生成按钮
binding.btnRegenerate.setOnClickListener {
showPasswordDialog {
generateOrRegenerateInvitation()
showPasswordDialog { password, dialog ->
generateOrRegenerateInvitation(password, dialog)
}
}
binding.sys.setOnClickListener {
checkPermissionsAndScan()
}
}
private fun checkPermissionsAndScan() {
val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
val storagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
} else {
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (cameraPermission == PackageManager.PERMISSION_GRANTED &&
storagePermission == PackageManager.PERMISSION_GRANTED
) {
startScan()
} else {
val permissionsToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_MEDIA_IMAGES
)
} else {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
requestPermissionsLauncher.launch(permissionsToRequest)
}
}
private fun startScan() {
MNScanManager.startScan(this, object : MNScanCallback {
override fun onActivityResult(resultCode: Int, data: Intent?) {
when (resultCode) {
MNScanManager.RESULT_SUCCESS -> {
val result = data?.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS)
if (!result.isNullOrEmpty()) {
ToastUtils.showToast(this@InvitationCodeActivity, "扫码成功: $result")
} else {
ToastUtils.showToast(this@InvitationCodeActivity, "扫码结果为空")
}
}
MNScanManager.RESULT_FAIL -> {
val error = data?.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR)
ToastUtils.showToast(this@InvitationCodeActivity, "扫码失败: $error")
}
MNScanManager.RESULT_CANCLE -> {
ToastUtils.showToast(this@InvitationCodeActivity, "扫码取消")
}
}
}
})
}
/**
* 生成或重新生成邀请码
*/
private fun generateOrRegenerateInvitation() {
// 根据当前选择的时效生成邀请码
generateQRCode()
showQrCode()
// 更新按钮文字为"重新生成"
binding.btnRegenerate.text = getString(R.string.regenerate)
hasGeneratedQrCode = true
ToastUtils.showSuccess(this, getString(R.string.operation_success))
private fun generateOrRegenerateInvitation(password: String, passwordDialog: PasswordInputDialog) {
if (isLoading) return
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.generateRecommendationCode(VerifyOptionPasswordRequest(password))
Log.d(TAG, "生成推荐码响应: code=${response.code}, msg=${response.msg}, data=${response.data}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
val parsed = parseRecommendationCode(response.data)
if (parsed != null) {
passwordDialog.dismiss()
generatedInviteCode = parsed.first
expireTimestamp = parsed.second
qrCodeContent =
"{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
Log.d(
TAG,
"推荐码生成成功: Code=$generatedInviteCode, expireTimestamp=$expireTimestamp, qrContent=$qrCodeContent"
)
generateQRCode()
showQrCode()
binding.btnRegenerate.text = getString(R.string.regenerate)
hasGeneratedQrCode = true
ToastUtils.showSuccess(this@InvitationCodeActivity, getString(R.string.operation_success))
} else {
Log.w(TAG, "推荐码解析失败, raw data=${response.data}")
passwordDialog.showError(getString(R.string.network_error))
}
} else {
Log.w(TAG, "生成推荐码失败: code=${response.code}, msg=${response.msg}")
passwordDialog.showError(
response.msg.ifEmpty { getString(R.string.password_error) }
)
}
}
} catch (e: Exception) {
Log.e(TAG, "生成推荐码异常", e)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
passwordDialog.showError(getString(R.string.network_error))
}
}
}
}
private fun showExpireTimeDialog() {
@@ -151,9 +279,6 @@ class InvitationCodeActivity : AppCompatActivity() {
binding.tvExpireTime.text = getString(R.string.expire_7d)
}
}
// 显示二维码并生成
showQrCode()
generateQRCode()
expireTimeDialog?.dismiss()
}
@@ -173,22 +298,28 @@ class InvitationCodeActivity : AppCompatActivity() {
}
private fun generateQRCode() {
// 检查二维码是否过期,如果过期则生成新的邀请码
val isExpired = expireTimestamp > 0 && System.currentTimeMillis() > expireTimestamp
val content = if (qrCodeContent.isNotEmpty() && !isExpired) qrCodeContent else generateInviteCode()
val qrBitmap = createQRCodeWithLogo(content, 400, 400)
if (qrCodeContent.isEmpty()) return
val qrBitmap = createQRCodeWithLogo(qrCodeContent, 400, 400)
binding.ivQrCode.setImageBitmap(qrBitmap)
}
private fun generateInviteCode(): String {
val timestamp = System.currentTimeMillis()
expireTimestamp = timestamp + expireTimeInMillis
generatedInviteCode = "INV${timestamp}"
qrCodeContent = "{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
// 输出日志,方便调试验证
android.util.Log.d("InvitationCode", "生成的二维码内容: $qrCodeContent")
android.util.Log.d("InvitationCode", "推荐码: $generatedInviteCode, 过期时间戳: $expireTimestamp")
return qrCodeContent
private fun parseRecommendationCode(data: Any?): Pair<String, Long>? {
if (data == null) return null
return when (data) {
is String -> {
if (data.isEmpty()) null
else Pair(data, System.currentTimeMillis() + expireTimeInMillis)
}
else -> {
val bean = Gson().fromJson(Gson().toJson(data), RecommendationCodeBean::class.java)
val code = bean.inviteCode?.takeIf { it.isNotEmpty() }
?: bean.code?.takeIf { it.isNotEmpty() }
?: return null
val expire = bean.expireTime ?: bean.expire
?: (System.currentTimeMillis() + expireTimeInMillis)
Pair(code, expire)
}
}
}
/*
* 这段代码生成带Logo的二维码
@@ -415,56 +546,62 @@ class InvitationCodeActivity : AppCompatActivity() {
/**
* 撤销邀请
*/
private fun cancelInvitation() {
// 撤销邀请逻辑
hideQrCode()
generatedInviteCode = ""
qrCodeContent = ""
expireTimestamp = 0
hasGeneratedQrCode = false
// 恢复按钮文字为"生成邀请码"
binding.btnRegenerate.text = getString(R.string.generate_invite_code)
// 显示提示
ToastUtils.showSuccess(this, getString(R.string.operation_success))
}
/**
* 显示密码输入对话框
* @param onSuccess 验证成功后的回调
*/
private fun showPasswordDialog(onSuccess: () -> Unit) {
val dialog = PasswordInputDialog(this)
dialog.setTitle(getString(R.string.input_op_pwd))
dialog.setOnConfirmListener { password ->
verifyPassword(password, dialog, onSuccess)
private fun cancelInvitation(password: String, passwordDialog: PasswordInputDialog) {
if (isLoading) return
if (generatedInviteCode.isEmpty()) {
passwordDialog.showError(getString(R.string.qr_code_expired))
return
}
dialog.show()
}
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
/**
* 验证操作密码
*/
private fun verifyPassword(password: String, dialog: PasswordInputDialog, onSuccess: () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.verifyOptionPassword(VerifyOptionPasswordRequest(password))
.revokeInvitation(VerifyOptionPasswordRequest(password))
Log.d(TAG, "撤销邀请响应: code=${response.code}, msg=${response.msg}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
dialog.dismiss()
onSuccess.invoke()
passwordDialog.dismiss()
hideQrCode()
generatedInviteCode = ""
qrCodeContent = ""
expireTimestamp = 0
hasGeneratedQrCode = false
binding.btnRegenerate.text = getString(R.string.generate_invite_code)
ToastUtils.showSuccess(this@InvitationCodeActivity, getString(R.string.operation_success))
} else {
dialog.showError(getString(R.string.password_error))
passwordDialog.showError(
response.msg.ifEmpty { getString(R.string.password_error) }
)
}
}
} catch (e: Exception) {
Log.e(TAG, "撤销邀请异常", e)
withContext(Dispatchers.Main) {
dialog.showError(getString(R.string.network_error))
isLoading = false
loadingDialog.dismiss()
passwordDialog.showError(getString(R.string.network_error))
}
}
}
}
/**
* 显示密码输入对话框
*/
private fun showPasswordDialog(onConfirm: (String, PasswordInputDialog) -> Unit) {
val dialog = PasswordInputDialog(this)
dialog.setTitle(getString(R.string.input_op_pwd))
dialog.setOnConfirmListener { password ->
onConfirm(password, dialog)
}
dialog.show()
}
override fun onDestroy() {
super.onDestroy()
expireTimeDialog?.dismiss()

View File

@@ -164,6 +164,8 @@
android:background="@drawable/rounded_blue_bg"
android:paddingHorizontal="12dp"
android:maxWidth="120dp"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -223,6 +225,8 @@
android:layout_marginTop="40dp"
android:text="@string/login"
android:textSize="18sp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
android:textColor="@color/white"
app:layout_constraintTop_toBottomOf="@+id/smsRowLayout"

View File

@@ -123,6 +123,8 @@
android:textColor="@color/white"
android:textSize="16dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -80,6 +80,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/select_wifi"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
</LinearLayout>

View File

@@ -135,6 +135,8 @@
android:text="@string/confirm"
android:textColor="@color/white"
android:textSize="14dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
</LinearLayout>

View File

@@ -105,6 +105,8 @@
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etConfirmPassword" />

View File

@@ -287,6 +287,8 @@
android:textColor="@color/white"
android:textSize="14dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -107,6 +107,8 @@
android:textSize="17sp"
android:textStyle="bold"
android:background="@drawable/bg_capture_face_button"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textColor="#F2FFFFFF" />
</LinearLayout>

View File

@@ -153,6 +153,8 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:elevation="0dp"
android:stateListAnimator="@null"
android:layout_marginStart="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -246,11 +248,13 @@
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_marginTop="32dp"
android:text="@string/determine"
android:text="@string/determine"
android:textAllCaps="false"
android:textSize="16sp"
app:cornerRadius="12dp"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -118,6 +118,7 @@
</FrameLayout>
<!-- 使用智能文字和图标 -->
<LinearLayout
android:id="@+id/sys"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -125,16 +126,16 @@
android:gravity="center_vertical">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/syis" />
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/syis" />
<TextView
android:textColor="@color/green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/use_smart_defense_app"
android:textColor="@color/black"
android:textSize="14sp" />
</LinearLayout>
@@ -211,6 +212,8 @@
android:layout_marginEnd="12dp"
android:text="@string/cancel_invite"
android:textColor="@color/green"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp"
android:background="@drawable/bg_blue_border_round"
android:textAllCaps="false" />

View File

@@ -149,6 +149,8 @@
android:text="@string/add_linked_user"
android:textColor="@color/white"
android:textSize="14dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
<!-- 悬浮加号按钮 -->
<ImageView

View File

@@ -148,6 +148,8 @@
android:background="@drawable/rounded_blue_bg"
android:paddingHorizontal="12dp"
android:maxWidth="120dp"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -293,6 +295,8 @@
android:textSize="16sp"
app:cornerRadius="12dp"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -35,6 +35,7 @@ tools:context=".login.RegisterSuccessActivity">
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -80,6 +81,8 @@ tools:context=".login.RegisterSuccessActivity">
android:textColor="@color/white"
android:textSize="14dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -91,6 +91,8 @@
android:paddingHorizontal="16dp"
android:text="@string/get_verification_code"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
</RelativeLayout>
@@ -131,6 +133,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/confirm_reset_password_btn"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp" />
</LinearLayout>

View File

@@ -81,6 +81,8 @@
android:text="@string/complete"
android:textColor="@color/white"
android:textSize="16dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textStyle="bold" />
</LinearLayout>

View File

@@ -76,6 +76,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/submit"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp" />
</LinearLayout>

View File

@@ -182,6 +182,8 @@
android:textSize="16dp"
android:background="@drawable/rounded_blue_bg"
android:padding="0dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:layout_margin="0dp"/>
</LinearLayout>

View File

@@ -72,7 +72,7 @@
android:layout_height="0dp"
android:layout_weight="1"/>
<Button
<android.widget.Button
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -80,7 +80,9 @@
android:layout_marginEnd="20dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="90dp"
android:backgroundTint="@color/green" />
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
</LinearLayout>

View File

@@ -80,6 +80,8 @@
android:background="@drawable/rounded_blue_bg"
android:text="@string/select_wifi"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B