Files
SecurityDefenseDevice/app/src/main/java/com/example/defenseapplication/personal/InvitationCodeActivity.kt
2026-05-13 17:46:21 +08:00

378 lines
13 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 android.widget.RadioButton
import android.widget.RadioGroup
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.databinding.DialogExpireTimeBinding
import com.example.defenseapplication.databinding.InvitationCodeActivityBinding
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 java.io.File
import java.io.FileOutputStream
import java.util.Hashtable
//邀请码页面
class InvitationCodeActivity : AppCompatActivity() {
private lateinit var binding: InvitationCodeActivityBinding
private var expireTimeInMillis: Long = 24 * 60 * 60 * 1000 // 默认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 // 时效选择弹窗
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()
}
private fun initListener() {
binding.ivBack.setOnClickListener {
finish()
}
binding.llExpireTime.setOnClickListener {
showExpireTimeDialog()
}
binding.llWechatShare.setOnClickListener {
// 防止多次点击间隔1秒
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime < 3000) {
return@setOnClickListener
}
lastClickTime = currentTime
// 微信分享功能
shareToWeChat()
}
}
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 = generateInviteCode()
val qrBitmap = createQRCodeWithLogo(content, 400, 400)
binding.ivQrCode.setImageBitmap(qrBitmap)
}
private fun generateInviteCode(): String {
// 生成包含邀请信息的JSON字符串
val timestamp = System.currentTimeMillis()
expireTimestamp = timestamp + expireTimeInMillis
generatedInviteCode = "INV${timestamp}"
qrCodeContent = "{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$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
// 设置二维码边距为最小1
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
)
}
}
}
// 在二维码中间绘制logo
val logoDrawable = ContextCompat.getDrawable(this, R.mipmap.loge)
if (logoDrawable != null) {
// logo大小为二维码的1/5
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
}
// 使用 FileProvider 获取内容 URI
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))
}
}
override fun onDestroy() {
super.onDestroy()
// 关闭弹窗,防止内存泄漏
expireTimeDialog?.dismiss()
}
}