生成二维码
This commit is contained in:
@@ -6,40 +6,43 @@ 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.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 = 24 * 60 * 60 * 1000 // 默认24小时
|
||||
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
|
||||
@@ -61,6 +64,9 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
binding.tvExpireTime.text = getString(R.string.expire_24h)
|
||||
// 默认显示灰色占位区域,二维码不可见
|
||||
hideQrCode()
|
||||
|
||||
// 页面加载时查询推荐码状态
|
||||
queryRecommendationCodeStatus()
|
||||
}
|
||||
|
||||
private fun initListener() {
|
||||
@@ -73,7 +79,7 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.llWechatShare.setOnClickListener {
|
||||
// 防止多次点击(间隔1秒)
|
||||
// 防止多次点击(间隔3秒)
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (currentTime - lastClickTime < 3000) {
|
||||
return@setOnClickListener
|
||||
@@ -82,6 +88,119 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
// 微信分享功能
|
||||
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() {
|
||||
@@ -108,6 +227,7 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
dialogBinding.tvConfirm.setOnClickListener {
|
||||
// 直接设置新的时效并生成二维码
|
||||
when (selectedExpireOption) {
|
||||
0 -> {
|
||||
expireTimeInMillis = EXPIRE_24H
|
||||
@@ -118,7 +238,7 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
binding.tvExpireTime.text = getString(R.string.expire_7d)
|
||||
}
|
||||
}
|
||||
// 选择后显示二维码并生成
|
||||
// 显示二维码并生成
|
||||
showQrCode()
|
||||
generateQRCode()
|
||||
expireTimeDialog?.dismiss()
|
||||
@@ -128,31 +248,31 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
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 content = if (qrCodeContent.isNotEmpty()) qrCodeContent else 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}"
|
||||
// 输出日志,方便调试验证
|
||||
android.util.Log.d("InvitationCode", "生成的二维码内容: $qrCodeContent")
|
||||
android.util.Log.d("InvitationCode", "推荐码: $generatedInviteCode, 过期时间戳: $expireTimestamp")
|
||||
return qrCodeContent
|
||||
}
|
||||
|
||||
@@ -161,7 +281,6 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
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()
|
||||
@@ -170,10 +289,8 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
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
|
||||
|
||||
@@ -194,10 +311,8 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// 在二维码中间绘制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
|
||||
@@ -218,51 +333,42 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -270,14 +376,12 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
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")
|
||||
@@ -297,12 +401,10 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
.setLineSpacing(0f, 1.4f)
|
||||
.build()
|
||||
|
||||
// 计算文字位置:距离二维码底部有固定间距
|
||||
val qrBottom = qrY + qrSize
|
||||
val marginBelowQr = (height * 0.04f).toInt() // 二维码下方间距
|
||||
val marginBelowQr = (height * 0.04f).toInt()
|
||||
val textY = qrBottom + marginBelowQr
|
||||
|
||||
// 向右偏移
|
||||
val rightOffset = (width * 0.35f).toInt()
|
||||
|
||||
canvas.save()
|
||||
@@ -310,7 +412,6 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
staticLayout.draw(canvas)
|
||||
canvas.restore()
|
||||
|
||||
|
||||
bitmap
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -342,7 +443,6 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 FileProvider 获取内容 URI
|
||||
val authority = "${packageName}.fileprovider"
|
||||
val contentUri = androidx.core.content.FileProvider.getUriForFile(this, authority, file)
|
||||
|
||||
@@ -350,11 +450,9 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
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
|
||||
@@ -369,9 +467,31 @@ class InvitationCodeActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销邀请
|
||||
*/
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user