commit 29b7cddd08d308fb6458eba962a2dbe0f622e316
parent 933ebfe23ad1a67baa2d2a2a327d7de9db71e508
Author: Iván Ávalos <avalos@disroot.org>
Date: Mon, 16 Feb 2026 23:09:40 +0100
[wallet] fix #11043 + refactor QR center log
Diffstat:
7 files changed, 111 insertions(+), 101 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
@@ -48,6 +48,7 @@ import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
import net.taler.merchantpos.databinding.FragmentProcessPaymentBinding
import androidx.core.graphics.createBitmap
+import net.taler.common.QrCodeManager
class ProcessPaymentFragment : Fragment() {
@@ -203,81 +204,18 @@ class ProcessPaymentFragment : Fragment() {
}
private fun makePaymentQrCode(text: String, size: Int): Bitmap {
- val qrBitmap = makeQrCode(
+ return makeQrCode(
text = text,
size = size,
margin = 1,
errorCorrection = ErrorCorrectionLevel.H,
+ centerLogo = ContextCompat.getDrawable(
+ requireContext(),
+ R.drawable.ic_taler_logo_qr,
+ ),
)
- val logoDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.ic_taler_logo_qr)
- ?: return qrBitmap
- return addCenteredLogo(qrBitmap, logoDrawable)
}
- private fun addCenteredLogo(qrBitmap: Bitmap, logoDrawable: Drawable): Bitmap {
- val result = qrBitmap.copy(ARGB_8888, true)
- val canvas = Canvas(result)
- val logoBitmap = drawableToBitmap(logoDrawable)
- var logoMaxWidth = (result.width * 0.30f).toInt()
- val logoAspectRatio = logoBitmap.width.toFloat() / logoBitmap.height.toFloat()
- var logoWidth = logoMaxWidth
- var logoHeight = (logoWidth / logoAspectRatio).toInt().coerceAtLeast(1)
- var horizontalPadding = (logoHeight * 0.12f).toInt()
- var verticalPadding = (logoHeight * 0.09f).toInt()
-
- val maxOcclusionRatio = 0.11f
- val currentOcclusionRatio =
- ((logoWidth + horizontalPadding * 2f) * (logoHeight + verticalPadding * 2f)) /
- (result.width.toFloat() * result.height.toFloat())
- if (currentOcclusionRatio > maxOcclusionRatio) {
- val scale = kotlin.math.sqrt(maxOcclusionRatio / currentOcclusionRatio)
- logoMaxWidth = (logoMaxWidth * scale).toInt().coerceAtLeast(1)
- logoWidth = logoMaxWidth
- logoHeight = (logoWidth / logoAspectRatio).toInt().coerceAtLeast(1)
- horizontalPadding = (horizontalPadding * scale).toInt()
- verticalPadding = (verticalPadding * scale).toInt()
- }
-
- val centerX = result.width / 2
- val centerY = result.height / 2
- val halfBackgroundWidth = (logoWidth / 2f) + horizontalPadding
- val halfBackgroundHeight = (logoHeight / 2f) + verticalPadding
- val backgroundRect = RectF(
- centerX - halfBackgroundWidth,
- centerY - halfBackgroundHeight,
- centerX + halfBackgroundWidth,
- centerY + halfBackgroundHeight,
- )
-
- val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
- style = Paint.Style.FILL
- color = android.graphics.Color.WHITE
- }
- val cornerRadius = halfBackgroundHeight // * 0.8f taler has circle in logo, so it can be fine
- canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint)
-
- val destinationRect = Rect(
- centerX - logoWidth / 2,
- centerY - logoHeight / 2,
- centerX + logoWidth / 2,
- centerY + logoHeight / 2,
- )
- canvas.drawBitmap(logoBitmap, null, destinationRect, Paint(Paint.ANTI_ALIAS_FLAG))
- return result
- }
-
- private fun drawableToBitmap(drawable: Drawable): Bitmap {
- if (drawable is BitmapDrawable && drawable.bitmap != null) {
- return drawable.bitmap
- }
- val width = drawable.intrinsicWidth.coerceAtLeast(1)
- val height = drawable.intrinsicHeight.coerceAtLeast(1)
- val bitmap = createBitmap(width, height)
- val canvas = Canvas(bitmap)
- drawable.setBounds(0, 0, canvas.width, canvas.height)
- drawable.draw(canvas)
- return bitmap
- }
}
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
@@ -17,11 +17,16 @@
package net.taler.common
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.BLACK
import android.graphics.Color.WHITE
-import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
import androidx.core.graphics.createBitmap
import androidx.core.graphics.set
import com.google.zxing.BarcodeFormat.QR_CODE
@@ -37,7 +42,8 @@ object QrCodeManager {
size: Int = 256,
margin: Int = 4,
errorCorrection: ErrorCorrectionLevel = ErrorCorrectionLevel.M,
- centerLogo: ((size: Int) -> Bitmap)? = null,
+ centerLogo: Drawable? = null,
+ drawBackground: Boolean = false,
): Bitmap {
val qrCodeWriter = QRCodeWriter()
val hints = mapOf(
@@ -54,19 +60,84 @@ object QrCodeManager {
}
}
- if (centerLogo != null) {
- val combined = createBitmap(bmp.width, bmp.height, bmp.config!!)
- val canvas = Canvas(combined)
- canvas.drawBitmap(bmp, Matrix(), null)
+ return if (centerLogo != null) {
+ addCenteredLogo(bmp, centerLogo, drawBackground)
+ } else {
+ bmp
+ }
+ }
+
+ private fun addCenteredLogo(
+ qrBitmap: Bitmap,
+ logoDrawable: Drawable,
+ drawBackground: Boolean = false,
+ ): Bitmap {
+ val result = qrBitmap.copy(ARGB_8888, true)
+ val canvas = Canvas(result)
+ val logoBitmap = drawableToBitmap(logoDrawable)
- val logo = centerLogo(canvas.width / 6)
- val centreX = (canvas.width - logo.width) / 2f
- val centreY = (canvas.height - logo.height) / 2f
+ var logoMaxWidth = (result.width * 0.22f).toInt()
+ val logoAspectRatio = logoBitmap.width.toFloat() / logoBitmap.height.toFloat()
+ var logoWidth = logoMaxWidth
+ var logoHeight = (logoWidth / logoAspectRatio).toInt().coerceAtLeast(1)
+ var horizontalPadding = (logoHeight * 0.12f).toInt()
+ var verticalPadding = (logoHeight * 0.09f).toInt()
- canvas.drawBitmap(logo, centreX, centreY, null)
- return combined
+ val maxOcclusionRatio = 0.11f
+ val currentOcclusionRatio =
+ ((logoWidth + horizontalPadding * 2f) * (logoHeight + verticalPadding * 2f)) /
+ (result.width.toFloat() * result.height.toFloat())
+ if (currentOcclusionRatio > maxOcclusionRatio) {
+ val scale = kotlin.math.sqrt(maxOcclusionRatio / currentOcclusionRatio)
+ logoMaxWidth = (logoMaxWidth * scale).toInt().coerceAtLeast(1)
+ logoWidth = logoMaxWidth
+ logoHeight = (logoWidth / logoAspectRatio).toInt().coerceAtLeast(1)
+ horizontalPadding = (horizontalPadding * scale).toInt()
+ verticalPadding = (verticalPadding * scale).toInt()
}
- return bmp
+ val centerX = result.width / 2
+ val centerY = result.height / 2
+
+ if (drawBackground) {
+ val halfBackgroundWidth = (logoWidth / 2f) + horizontalPadding
+ val halfBackgroundHeight = (logoHeight / 2f) + verticalPadding
+ val backgroundRect = RectF(
+ centerX - halfBackgroundWidth,
+ centerY - halfBackgroundHeight,
+ centerX + halfBackgroundWidth,
+ centerY + halfBackgroundHeight,
+ )
+
+ val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ color = WHITE
+ }
+ val cornerRadius =
+ halfBackgroundHeight // * 0.8f taler has circle in logo, so it can be fine
+ canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint)
+ }
+
+ val destinationRect = Rect(
+ centerX - logoWidth / 2,
+ centerY - logoHeight / 2,
+ centerX + logoWidth / 2,
+ centerY + logoHeight / 2,
+ )
+ canvas.drawBitmap(logoBitmap, null, destinationRect, Paint(Paint.ANTI_ALIAS_FLAG))
+ return result
+ }
+
+ private fun drawableToBitmap(drawable: Drawable): Bitmap {
+ if (drawable is BitmapDrawable && drawable.bitmap != null) {
+ return drawable.bitmap
+ }
+ val width = drawable.intrinsicWidth.coerceAtLeast(1)
+ val height = drawable.intrinsicHeight.coerceAtLeast(1)
+ val bitmap = createBitmap(width, height)
+ val canvas = Canvas(bitmap)
+ drawable.setBounds(0, 0, canvas.width, canvas.height)
+ drawable.draw(canvas)
+ return bitmap
}
}
diff --git a/merchant-terminal/src/main/res/drawable/ic_taler_logo_qr.xml b/taler-kotlin-android/src/main/res/drawable/ic_taler_logo_qr.xml
diff --git a/wallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt b/wallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt
@@ -43,9 +43,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat
import net.taler.wallet.R
@Composable
@@ -141,6 +143,8 @@ fun ExpandableSection(
fun ExpandableCardPreview(
section: Boolean = false,
) {
+ val context = LocalContext.current
+
TalerSurface {
var expanded by remember { mutableStateOf(true) }
ExpandableCard(
@@ -152,7 +156,7 @@ fun ExpandableCardPreview(
QrCodeUriComposable(
talerUri = "taler://withdraw-exchange",
clipBoardLabel = "",
- centerLogo = painterResource(R.drawable.ic_swiss_qr),
+ centerLogo = ContextCompat.getDrawable(context, R.drawable.ic_swiss_qr),
showContents = false,
)
}
diff --git a/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt b/wallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt
@@ -16,6 +16,7 @@
package net.taler.wallet.compose
+import android.graphics.drawable.Drawable
import androidx.compose.foundation.Image
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
@@ -66,30 +67,20 @@ import kotlin.let
fun ColumnScope.QrCodeUriComposable(
talerUri: String,
clipBoardLabel: String,
- centerLogo: Painter? = null,
+ centerLogo: Drawable? = null,
+ drawCenterLogoBackground: Boolean = false,
buttonText: String = stringResource(R.string.copy),
showContents: Boolean = true,
shareAsQrCode: Boolean = false,
inBetween: (@Composable ColumnScope.() -> Unit)? = null,
) {
val qrCodeSize = getQrCodeSize()
- val density = LocalDensity.current
- val direction = LocalLayoutDirection.current
-
val qrState = produceState<ImageBitmap?>(null) {
value = QrCodeManager.makeQrCode(
talerUri,
qrCodeSize.value.toInt(),
- centerLogo = centerLogo?.let {{ size ->
- centerLogo.toImageBitmap(
- Size(
- size * (centerLogo.intrinsicSize.width / centerLogo.intrinsicSize.height),
- size.toFloat(),
- ),
- density,
- direction,
- ).asAndroidBitmap()
- }},
+ centerLogo = centerLogo,
+ drawBackground = drawCenterLogoBackground,
).asImageBitmap()
}
diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt
@@ -29,11 +29,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat
import net.taler.common.Amount
import net.taler.common.CurrencySpecification
import net.taler.common.Timestamp
@@ -106,9 +108,8 @@ fun ColumnScope.PeerQrCode(
talerUri: String?,
instructionResId: Int,
) {
- remember(state) {
- 10.dp
- }
+ val context = LocalContext.current
+
if (state == TransactionState(Pending) && state.minor != MergeKycRequired) {
Text(
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp),
@@ -122,7 +123,8 @@ fun ColumnScope.PeerQrCode(
QrCodeUriComposable(
talerUri = talerUri,
clipBoardLabel = "Push payment",
- centerLogo = painterResource(R.drawable.ic_taler_qr),
+ centerLogo = ContextCompat.getDrawable(context, R.drawable.ic_taler_logo_qr),
+ drawCenterLogoBackground = true,
buttonText = stringResource(id = R.string.copy),
) {
Text(
diff --git a/wallet/src/main/java/net/taler/wallet/transfer/PaytoQrCard.kt b/wallet/src/main/java/net/taler/wallet/transfer/PaytoQrCard.kt
@@ -22,9 +22,11 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat
import net.taler.wallet.R
import net.taler.wallet.compose.ExpandableCard
import net.taler.wallet.compose.QrCodeUriComposable
@@ -44,6 +46,8 @@ fun PaytoQrCard(
else -> return
}
+ val context = LocalContext.current
+
ExpandableCard(
expanded = expanded,
setExpanded = setExpanded,
@@ -57,9 +61,9 @@ fun PaytoQrCard(
showContents = true,
shareAsQrCode = true,
centerLogo = when (qrCode.type) {
- SPC -> painterResource(R.drawable.ic_swiss_qr)
+ SPC -> ContextCompat.getDrawable(context, R.drawable.ic_swiss_qr)
else -> null
- }
+ },
)
Spacer(Modifier.height(8.dp))