commit af7a85e5834fccb3ad991826f87fc67e717046bf
parent f7f4133493878258740a7d016a5fef801458df95
Author: Iván Ávalos <avalos@disroot.org>
Date: Tue, 17 Mar 2026 15:05:55 +0100
[shared] improve loading of QR codes
Diffstat:
5 files changed, 59 insertions(+), 46 deletions(-)
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
@@ -25,15 +25,16 @@ import androidx.activity.OnBackPressedCallback
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.NavOptions
import androidx.navigation.fragment.findNavController
import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
import com.google.android.material.snackbar.Snackbar
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import kotlinx.coroutines.launch
import net.taler.common.QrCodeManager.makeQrCode
import net.taler.common.copyToClipBoard
import net.taler.common.fadeIn
@@ -197,7 +198,9 @@ class ProcessPaymentFragment : Fragment() {
ui.qrcodeView.post {
val blockSize = minOf(ui.qrcodeView.width, ui.qrcodeView.height).coerceAtLeast(256)
val qrSize = (blockSize * 0.88f).toInt().coerceAtLeast(256)
- currentQrBitmap = makePaymentQrCode(text, qrSize)
+ lifecycleScope.launch {
+ currentQrBitmap = makePaymentQrCode(text, qrSize)
+ }
ui.qrcodeView.setContent {
PosTheme {
AnimatedQrCodeComposable(
@@ -211,7 +214,7 @@ class ProcessPaymentFragment : Fragment() {
}
}
- private fun makePaymentQrCode(text: String, size: Int): Bitmap {
+ private suspend fun makePaymentQrCode(text: String, size: Int): Bitmap {
return makeQrCode(
text = text,
size = size,
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt b/taler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt
@@ -20,7 +20,6 @@ import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import android.graphics.Bitmap.Config.RGB_565
import android.graphics.Canvas
-import android.graphics.Color
import android.graphics.Color.BLACK
import android.graphics.Color.WHITE
import android.graphics.Paint
@@ -35,6 +34,8 @@ import com.google.zxing.EncodeHintType.ERROR_CORRECTION
import com.google.zxing.EncodeHintType.MARGIN
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
enum class QrLogoSize(val size: Float) {
SMALL(0.15f),
@@ -44,7 +45,7 @@ enum class QrLogoSize(val size: Float) {
object QrCodeManager {
- fun makeQrCode(
+ suspend fun makeQrCode(
text: String,
size: Int = 256,
margin: Int = 2,
@@ -55,7 +56,7 @@ object QrCodeManager {
darkColor: Int = BLACK,
lightColor: Int = WHITE,
trimQuietZone: Boolean = false,
- ): Bitmap {
+ ): Bitmap = withContext(Dispatchers.IO) {
val qrCodeWriter = QRCodeWriter()
val hints = mapOf(
MARGIN to margin.coerceAtLeast(0),
@@ -73,7 +74,7 @@ object QrCodeManager {
val qrBitmap = if (trimQuietZone) trimQrQuietZone(bmp, lightColor) else bmp
- return if (centerLogo != null && centerLogoSize != null && drawBackground != null) {
+ return@withContext if (centerLogo != null && centerLogoSize != null && drawBackground != null) {
addCenteredLogo(qrBitmap, centerLogo, centerLogoSize, drawBackground, lightColor)
} else {
qrBitmap
diff --git a/taler-kotlin-android/src/main/java/net/taler/lib/android/AnimatedQrCodeComposable.kt b/taler-kotlin-android/src/main/java/net/taler/lib/android/AnimatedQrCodeComposable.kt
@@ -35,25 +35,29 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
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.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
+import androidx.core.graphics.toColorInt
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import net.taler.common.QrCodeManager.makeQrCode
@@ -79,8 +83,7 @@ fun AnimatedQrCodeComposable(
),
label = "qrStripeAngle",
)
-
- val stripeColor = MaterialTheme.colorScheme.primary
+ val stripeColor = Color("#3047a3".toColorInt()) // FIXME: load from light-mode primary
val stripeBase = Color.White //MaterialTheme.colorScheme.surfaceVariant
val stripeSoft = remember(stripeBase, stripeColor) {
lerp(stripeBase, stripeColor, 0.55f)
@@ -116,7 +119,7 @@ fun AnimatedQrCodeComposable(
contentAlignment = Alignment.Center,
) {
val density = LocalDensity.current
- var drawSize by remember { mutableStateOf<Dp?>(null) }
+ var drawSize by remember { mutableStateOf<Dp?>(379.dp) }
Box(
modifier = Modifier
@@ -128,11 +131,13 @@ fun AnimatedQrCodeComposable(
)
Canvas(
- modifier = Modifier.fillMaxSize(),
+ modifier = Modifier
+ .fillMaxSize()
+ .onGloballyPositioned { with(density) {
+ drawSize = it.size.width.toDp()
+ } },
) {
val s = with(density) { size.width.toDp() }
- drawSize = s
-
val cornerRadius = s * qrCornerRadiusFraction
val stripeWidth = s * QR_STRIPE_WIDTH
val stripePx = stripeWidth.toPx()
@@ -157,35 +162,42 @@ fun AnimatedQrCodeComposable(
)
}
- val blockSize = drawSize ?: return@Box
- val qrSize = blockSize * QR_DATA_SIZE
- val logoWidth = blockSize * QR_LOGO_SIZE
- val qrSizePx = with(LocalDensity.current) { qrSize.roundToPx().coerceAtLeast(256) }
- val qrBitmap = remember(link, qrSizePx) {
- makeQrCode(
- text = link,
- size = qrSizePx,
- margin = 0,
- errorCorrection = ErrorCorrectionLevel.H,
- centerLogo = null,
- drawBackground = true,
- trimQuietZone = true,
- )
- }
+ drawSize?.let { drawSize ->
+ val qrSize = drawSize * QR_DATA_SIZE
+ val qrSizePx = with(density) {
+ qrSize.roundToPx().coerceAtLeast(256) }
+ val logoSize = drawSize * QR_LOGO_SIZE
- Image(
- bitmap = qrBitmap.asImageBitmap(),
- contentDescription = null,
- contentScale = ContentScale.FillBounds,
- modifier = Modifier.size(qrSize).background(Color.White),
- )
+ val qrBitmap by produceState<ImageBitmap?>(null) {
+ value = makeQrCode(
+ text = link,
+ size = qrSizePx,
+ margin = 0,
+ errorCorrection = ErrorCorrectionLevel.H,
+ centerLogo = null,
+ drawBackground = true,
+ trimQuietZone = true,
+ ).asImageBitmap()
+ }
- if (logoPainter != null) {
- Image(
- painter = logoPainter,
- contentDescription = null,
- modifier = Modifier.width(logoWidth),
- )
+ qrBitmap?.let { qr ->
+ Image(
+ bitmap = qr,
+ contentDescription = null,
+ contentScale = ContentScale.FillBounds,
+ modifier = Modifier
+ .size(qrSize)
+ .background(Color.White),
+ )
+
+ if (logoPainter != null) {
+ Image(
+ painter = logoPainter,
+ contentDescription = null,
+ modifier = Modifier.width(logoSize),
+ )
+ }
+ }
}
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt b/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt
@@ -98,10 +98,7 @@ fun ColumnScope.QrCodeUriComposable(
is QrCodeParams.Custom -> params.centerLogoSize
},
- drawBackground = when (params) {
- QrCodeParams.Taler -> true
- is QrCodeParams.Custom -> false
- }
+ drawBackground = false,
)
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
@@ -105,7 +105,7 @@ fun ColumnScope.PeerQrCode(
) {
if (state == TransactionState(Pending) && state.minor != KycRequired) {
Text(
- modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
+ modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 8.dp),
style = MaterialTheme.typography.bodyLarge,
text = stringResource(id = instructionResId, amount.toString()),
textAlign = TextAlign.Center,