Files
SecurityDefenseDevice/app/src/main/java/com/example/defenseapplication/personal/InvitationCodeActivity.kt
2026-05-26 11:07:14 +08:00

497 lines
18 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.example.defenseapplication.personal
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.Bundle
import android.os.Environment
import android.text.TextPaint
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomsheet.BottomSheetDialog
import androidx.core.content.ContextCompat
import com.example.defenseapplication.R
import com.example.defenseapplication.bean.VerifyOptionPasswordRequest
import com.example.defenseapplication.databinding.DialogExpireTimeBinding
import com.example.defenseapplication.databinding.InvitationCodeActivityBinding
import com.example.defenseapplication.utils.RetrofitNetwork
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.util.Hashtable
//邀请二维码
class InvitationCodeActivity : AppCompatActivity() {
private lateinit var binding: InvitationCodeActivityBinding
private var expireTimeInMillis: Long = EXPIRE_24H // 默认24小时
private var selectedExpireOption: Int = 0 // 0: 24h, 1: 7d默认选中24h
private var generatedInviteCode: String = "" // 生成的邀请码
private var expireTimestamp: Long = 0 // 过期时间戳
private var qrCodeContent: String = "" // 生成的二维码内容
private var lastClickTime: Long = 0 // 上次点击时间,用于防止多次点击
private var expireTimeDialog: BottomSheetDialog? = null // 时效选择弹窗
private var isLoading = false // 是否正在加载
companion object {
const val EXPIRE_24H = 24 * 60 * 60 * 1000L
const val EXPIRE_7D = 7 * 24 * 60 * 60 * 1000L
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = InvitationCodeActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this)
.statusBarDarkFont(true)
.titleBar(binding.topBar)
.init()
initListener()
// 默认显示时效为24h
binding.tvExpireTime.text = getString(R.string.expire_24h)
// 默认显示灰色占位区域,二维码不可见
hideQrCode()
// 页面加载时查询推荐码状态
queryRecommendationCodeStatus()
}
private fun initListener() {
binding.ivBack.setOnClickListener {
finish()
}
binding.llExpireTime.setOnClickListener {
showExpireTimeDialog()
}
binding.llWechatShare.setOnClickListener {
// 防止多次点击间隔3秒
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime < 3000) {
return@setOnClickListener
}
lastClickTime = currentTime
// 微信分享功能
shareToWeChat()
}
// 撤销邀请按钮
binding.btnCancelInvite.setOnClickListener {
cancelInvitation()
}
// 重新生成按钮
binding.btnRegenerate.setOnClickListener {
regenerateInvitation()
}
}
/**
* 查询推荐码状态
*/
private fun queryRecommendationCodeStatus() {
isLoading = true
CoroutineScope(Dispatchers.IO).launch {
try {
// 注意接口需要传入code参数但我们可能不知道当前的推荐码
// 这里使用空字符串或默认值进行查询
val response = RetrofitNetwork.getNetworkService().selectRecommendationCode("")
withContext(Dispatchers.Main) {
isLoading = false
if (response.code == 200) {
// 有推荐码,解析返回的数据
handleRecommendationCodeResponse(response.data)
} else {
// 没有推荐码或查询失败生成默认24小时的推荐码
generateDefaultRecommendationCode()
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
isLoading = false
// 网络异常生成默认24小时的推荐码
generateDefaultRecommendationCode()
}
}
}
}
/**
* 处理推荐码查询响应
*/
private fun handleRecommendationCodeResponse(data: Any?) {
// 解析返回的数据,获取推荐码和过期时间
// 这里需要根据实际接口返回格式进行解析
if (data is Map<*, *>) {
generatedInviteCode = data["code"]?.toString() ?: ""
expireTimestamp = data["expireTime"]?.toString()?.toLongOrNull() ?: 0L
// 检查推荐码是否过期
if (expireTimestamp > 0 && System.currentTimeMillis() < expireTimestamp) {
// 推荐码有效,显示二维码
qrCodeContent = "{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
showQrCode()
generateQRCode()
// 根据过期时间设置显示的时效
val remainingTime = expireTimestamp - System.currentTimeMillis()
if (remainingTime > EXPIRE_24H) {
// 超过24小时认为是7天的
expireTimeInMillis = EXPIRE_7D
selectedExpireOption = 1
binding.tvExpireTime.text = getString(R.string.expire_7d)
} else {
expireTimeInMillis = EXPIRE_24H
selectedExpireOption = 0
binding.tvExpireTime.text = getString(R.string.expire_24h)
}
} else {
// 推荐码已过期生成新的默认24小时推荐码
generateDefaultRecommendationCode()
}
} else {
// 数据格式不对,生成新的推荐码
generateDefaultRecommendationCode()
}
}
/**
* 生成默认24小时的推荐码
*/
private fun generateDefaultRecommendationCode() {
expireTimeInMillis = EXPIRE_24H
selectedExpireOption = 0
binding.tvExpireTime.text = getString(R.string.expire_24h)
// 生成推荐码本地生成实际应该调用API
generateQRCode()
showQrCode()
}
/**
* 处理生成推荐码成功
*/
private fun handleGenerateSuccess(data: Any?, expireDays: Int) {
// 解析返回的数据
if (data is Map<*, *>) {
generatedInviteCode = data["code"]?.toString() ?: ""
expireTimestamp = data["expireTime"]?.toString()?.toLongOrNull() ?: (System.currentTimeMillis() + expireTimeInMillis)
qrCodeContent = "{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
showQrCode()
generateQRCode()
} else {
// 如果接口没有返回推荐码,本地生成一个
generateInviteCode()
showQrCode()
generateQRCode()
}
}
private fun showExpireTimeDialog() {
val dialogBinding = DialogExpireTimeBinding.inflate(LayoutInflater.from(this))
expireTimeDialog = BottomSheetDialog(this, R.style.BottomSheetDialog)
expireTimeDialog?.setContentView(dialogBinding.root)
// 设置选中状态
when (selectedExpireOption) {
0 -> dialogBinding.rb24h.isChecked = true
1 -> dialogBinding.rb7d.isChecked = true
}
dialogBinding.radioGroup.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.rb24h -> selectedExpireOption = 0
R.id.rb7d -> selectedExpireOption = 1
}
}
dialogBinding.tvCancel.setOnClickListener {
expireTimeDialog?.dismiss()
}
dialogBinding.tvConfirm.setOnClickListener {
// 直接设置新的时效并生成二维码
when (selectedExpireOption) {
0 -> {
expireTimeInMillis = EXPIRE_24H
binding.tvExpireTime.text = getString(R.string.expire_24h)
}
1 -> {
expireTimeInMillis = EXPIRE_7D
binding.tvExpireTime.text = getString(R.string.expire_7d)
}
}
// 显示二维码并生成
showQrCode()
generateQRCode()
expireTimeDialog?.dismiss()
}
expireTimeDialog?.show()
}
private fun hideQrCode() {
binding.ivQrCode.visibility = View.GONE
binding.ivQrLogo.visibility = View.GONE
binding.vQrPlaceholder.visibility = View.VISIBLE
}
private fun showQrCode() {
binding.vQrPlaceholder.visibility = View.GONE
binding.ivQrCode.visibility = View.VISIBLE
binding.ivQrLogo.visibility = View.VISIBLE
}
private fun generateQRCode() {
val content = if (qrCodeContent.isNotEmpty()) qrCodeContent else generateInviteCode()
val qrBitmap = createQRCodeWithLogo(content, 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 createQRCodeWithLogo(content: String, width: Int, height: Int): Bitmap? {
return try {
val hints = Hashtable<EncodeHintType, Any>()
hints[EncodeHintType.CHARACTER_SET] = "UTF-8"
hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H
hints[EncodeHintType.MARGIN] = 1
val writer = QRCodeWriter()
val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints)
val qrBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(qrBitmap)
canvas.drawColor(Color.WHITE)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = Color.BLACK
val cellWidth = width.toFloat() / bitMatrix.width
val cellHeight = height.toFloat() / bitMatrix.height
for (x in 0 until bitMatrix.width) {
for (y in 0 until bitMatrix.height) {
if (bitMatrix.get(x, y)) {
canvas.drawRect(
x * cellWidth,
y * cellHeight,
(x + 1) * cellWidth,
(y + 1) * cellHeight,
paint
)
}
}
}
val logoDrawable = ContextCompat.getDrawable(this, R.mipmap.loge)
if (logoDrawable != null) {
val logoSize = width / 5
val logoX = (width - logoSize) / 2
val logoY = (height - logoSize) / 2
val logoBitmap = Bitmap.createBitmap(logoSize, logoSize, Bitmap.Config.ARGB_8888)
val logoCanvas = Canvas(logoBitmap)
logoDrawable.setBounds(0, 0, logoSize, logoSize)
logoDrawable.draw(logoCanvas)
canvas.drawBitmap(logoBitmap, logoX.toFloat(), logoY.toFloat(), null)
}
qrBitmap
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private fun shareToWeChat() {
if (expireTimestamp == 0L || System.currentTimeMillis() > expireTimestamp) {
ToastUtils.showError(this, getString(R.string.qr_code_expired))
return
}
val shareBitmap = generateSharePoster()
if (shareBitmap == null) {
ToastUtils.showError(this, getString(R.string.share_failed))
return
}
val imagePath = saveBitmapToFile(shareBitmap)
if (imagePath.isEmpty()) {
ToastUtils.showError(this, getString(R.string.share_failed))
return
}
shareImage(imagePath)
}
private fun generateSharePoster(): Bitmap? {
return try {
val posterBackground = ContextCompat.getDrawable(this, R.drawable.share_poster_empty)
?: return null
val width = posterBackground.intrinsicWidth
val height = posterBackground.intrinsicHeight
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
posterBackground.setBounds(0, 0, width, height)
posterBackground.draw(canvas)
val qrCode = createQRCodeWithLogo(qrCodeContent, 300, 300)
?: return bitmap
val qrSize = (width * 0.38f).toInt()
val qrX = (width - qrSize) / 2
val qrY = (height * 0.60f).toInt()
val scaledQr = Bitmap.createScaledBitmap(qrCode, qrSize, qrSize, true)
canvas.drawBitmap(scaledQr, qrX.toFloat(), qrY.toFloat(), null)
val expireText = if (expireTimeInMillis == EXPIRE_24H) {
getString(R.string.expire_24h)
} else {
getString(R.string.expire_7d)
}
val tipsText = getString(R.string.share_poster_tips, expireText)
val tipsPaint = android.text.TextPaint().apply {
color = Color.parseColor("#0475F3")
textSize = 36f
isAntiAlias = true
textAlign = Paint.Align.CENTER
isFakeBoldText = false
}
val horizontalPadding = (width * 0.12f).toInt()
val textWidth = width - horizontalPadding * 2 - (width * 0.08f).toInt()
val staticLayout = android.text.StaticLayout.Builder.obtain(
tipsText, 0, tipsText.length, tipsPaint, textWidth
)
.setAlignment(android.text.Layout.Alignment.ALIGN_CENTER)
.setLineSpacing(0f, 1.4f)
.build()
val qrBottom = qrY + qrSize
val marginBelowQr = (height * 0.04f).toInt()
val textY = qrBottom + marginBelowQr
val rightOffset = (width * 0.35f).toInt()
canvas.save()
canvas.translate((horizontalPadding + rightOffset).toFloat(), textY.toFloat())
staticLayout.draw(canvas)
canvas.restore()
bitmap
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private fun saveBitmapToFile(bitmap: Bitmap): String {
return try {
val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
?: return ""
val file = File(storageDir, "share_poster_${System.currentTimeMillis()}.png")
val fos = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.flush()
fos.close()
file.absolutePath
} catch (e: Exception) {
e.printStackTrace()
""
}
}
private fun shareImage(imagePath: String) {
val file = File(imagePath)
if (!file.exists()) {
ToastUtils.showError(this, getString(R.string.share_failed))
return
}
val authority = "${packageName}.fileprovider"
val contentUri = androidx.core.content.FileProvider.getUriForFile(this, authority, file)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/*"
putExtra(Intent.EXTRA_STREAM, contentUri)
putExtra(Intent.EXTRA_TEXT, getString(R.string.invite_friend))
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
val resInfoList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
for (resolveInfo in resInfoList) {
val packageName = resolveInfo.activityInfo.packageName
grantUriPermission(packageName, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val chooser = Intent.createChooser(intent, getString(R.string.share_to))
if (intent.resolveActivity(packageManager) != null) {
startActivity(chooser)
} else {
ToastUtils.showError(this, getString(R.string.no_share_app))
}
}
/**
* 撤销邀请
*/
private fun cancelInvitation() {
// 撤销邀请逻辑
hideQrCode()
generatedInviteCode = ""
qrCodeContent = ""
expireTimestamp = 0
// 显示提示
ToastUtils.showSuccess(this, getString(R.string.operation_success))
}
/**
* 重新生成邀请码
*/
private fun regenerateInvitation() {
// 根据当前选择的时效重新生成邀请码
generateQRCode()
showQrCode()
ToastUtils.showSuccess(this, getString(R.string.operation_success))
}
override fun onDestroy() {
super.onDestroy()
expireTimeDialog?.dismiss()
}
}