fix(shared): keep dense transfer QR codes scannable

This commit is contained in:
2026-07-15 01:10:48 +02:00
parent b602a3acb6
commit 37c364d66a
3 changed files with 59 additions and 4 deletions

View File

@@ -37,8 +37,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -59,7 +59,6 @@ import com.vnidrop.app.ui.state.progressForReceiver
import com.vnidrop.app.ui.theme.LocalVniDropColors
import org.jetbrains.compose.resources.decodeToImageBitmap
import org.jetbrains.compose.resources.stringResource
import qrcode.QRCode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import vnidrop.shared.generated.resources.*
@@ -247,7 +246,7 @@ internal fun TransferSharePanel(
val renderedBitmap by produceState(qrBitmap, ticket, qrBitmap) {
if (value == null) {
value = withContext(Dispatchers.Default) {
runCatching { QRCode.ofSquares().withSize(8).build(ticket).renderToBytes().decodeToImageBitmap() }.getOrNull()
runCatching { buildTransferQrCode(ticket).renderToBytes().decodeToImageBitmap() }.getOrNull()
}
value?.let { onQrRendered(ticket, it) }
}
@@ -259,7 +258,12 @@ internal fun TransferSharePanel(
color = Color.White,
) {
if (renderedQr != null) {
Image(renderedQr, null, Modifier.padding(14.dp).fillMaxSize().clip(RoundedCornerShape(8.dp)))
Image(
bitmap = renderedQr,
contentDescription = null,
modifier = Modifier.padding(14.dp).fillMaxSize(),
filterQuality = FilterQuality.None,
)
} else {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() }
}

View File

@@ -0,0 +1,29 @@
package com.vnidrop.app.feature.send
import qrcode.QRCode
import qrcode.raw.ErrorCorrectionLevel
private const val QrCellSize = 8
private const val QrQuietZoneModules = 4
// qrcode-kotlin 4.5.0 falls back to version 40 above version 20's byte capacity.
private val QrLowErrorCorrectionByteCapacities = intArrayOf(
17, 32, 53, 78, 106, 134, 154, 192, 230, 271,
321, 367, 425, 458, 520, 586, 644, 718, 792, 858,
929, 1003, 1091, 1171, 1273, 1367, 1465, 1528, 1628, 1732,
1840, 1952, 2068, 2188, 2303, 2431, 2563, 2699, 2809, 2953,
)
internal fun buildTransferQrCode(ticket: String): QRCode =
QRCode.ofSquares()
.withSize(QrCellSize)
.withInnerSpacing(0)
.withMargin(QrQuietZoneModules * QrCellSize)
.withErrorCorrectionLevel(ErrorCorrectionLevel.LOW)
.withInformationDensity(transferQrInformationDensity(ticket))
.build(ticket)
internal fun transferQrInformationDensity(ticket: String): Int {
val byteCount = ticket.encodeToByteArray().size
val index = QrLowErrorCorrectionByteCapacities.indexOfFirst { byteCount <= it }
return if (index >= 0) index + 1 else throw IllegalArgumentException("The invitation is too large for a QR code")
}