378 lines
13 KiB
Kotlin
378 lines
13 KiB
Kotlin
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()
|
||
}
|
||
}
|