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