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() 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() } }