taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit 08977f5e2b79ad4ab3ef79cc97511a792dfbbb13
parent 139eb9b3bd91317cbab32d7f0e4e4fcac20044a5
Author: Iván Ávalos <avalos@disroot.org>
Date:   Sat, 14 Feb 2026 16:25:50 +0100

[wallet] add Taler logo to taler:// QR and Swiss flag to Swiss QR

Diffstat:
Mtaler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt | 24+++++++++++++++++++++---
Mwallet/src/main/java/net/taler/wallet/Utils.kt | 25++++++++++++++++++++++---
Mwallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt | 3+++
Mwallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt | 33+++++++++++++++++++++++++++------
Mwallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt | 2++
Mwallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt | 1-
Mwallet/src/main/java/net/taler/wallet/transfer/PaytoQrCard.kt | 5+++++
Awallet/src/main/res/drawable/ic_taler_qr.xml | 32++++++++++++++++++++++++++++++++
8 files changed, 112 insertions(+), 13 deletions(-)

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 @@ -18,8 +18,12 @@ package net.taler.common import android.graphics.Bitmap 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 androidx.core.graphics.createBitmap +import androidx.core.graphics.set import com.google.zxing.BarcodeFormat.QR_CODE import com.google.zxing.EncodeHintType.ERROR_CORRECTION import com.google.zxing.EncodeHintType.MARGIN @@ -33,6 +37,7 @@ object QrCodeManager { size: Int = 256, margin: Int = 4, errorCorrection: ErrorCorrectionLevel = ErrorCorrectionLevel.M, + centerLogo: ((size: Int) -> Bitmap)? = null, ): Bitmap { val qrCodeWriter = QRCodeWriter() val hints = mapOf( @@ -42,13 +47,26 @@ object QrCodeManager { val bitMatrix = qrCodeWriter.encode(text, QR_CODE, size, size, hints) val height = bitMatrix.height val width = bitMatrix.width - val bmp = Bitmap.createBitmap(width, height, RGB_565) + val bmp = createBitmap(width, height, RGB_565) for (x in 0 until width) { for (y in 0 until height) { - bmp.setPixel(x, y, if (bitMatrix.get(x, y)) BLACK else WHITE) + bmp[x, y] = if (bitMatrix.get(x, y)) BLACK else WHITE } } + + if (centerLogo != null) { + val combined = createBitmap(bmp.width, bmp.height, bmp.config!!) + val canvas = Canvas(combined) + canvas.drawBitmap(bmp, Matrix(), null) + + val logo = centerLogo(canvas.width / 6) + val centreX = (canvas.width - logo.width) / 2f + val centreY = (canvas.height - logo.height) / 2f + + canvas.drawBitmap(logo, centreX, centreY, null) + return combined + } + return bmp } - } diff --git a/wallet/src/main/java/net/taler/wallet/Utils.kt b/wallet/src/main/java/net/taler/wallet/Utils.kt @@ -44,13 +44,19 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.drawscope.CanvasDrawScope +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import net.taler.common.Amount import net.taler.common.AmountParserException @@ -200,4 +206,17 @@ fun Modifier.systemBarsPaddingHorizontal() = @Composable fun Modifier.systemBarsPaddingAllExceptTop() = - windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)) -\ No newline at end of file + windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)) + +fun Painter.toImageBitmap( + size: Size, + density: Density, + layoutDirection: LayoutDirection, +): ImageBitmap { + val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt()) + val canvas = Canvas(bitmap) + CanvasDrawScope().draw(density, layoutDirection, canvas, size) { + draw(size) + } + return bitmap +} +\ No newline at end of file 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,8 +43,10 @@ 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.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import net.taler.wallet.R @Composable fun ExpandableCard( @@ -150,6 +152,7 @@ fun ExpandableCardPreview( QrCodeUriComposable( talerUri = "taler://withdraw-exchange", clipBoardLabel = "", + centerLogo = painterResource(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 @@ -42,10 +42,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.produceState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.Dp @@ -54,22 +59,38 @@ import androidx.compose.ui.unit.min import net.taler.common.QrCodeManager import net.taler.common.copyToClipBoard import net.taler.wallet.R +import net.taler.wallet.toImageBitmap +import kotlin.let @Composable fun ColumnScope.QrCodeUriComposable( talerUri: String, clipBoardLabel: String, + centerLogo: Painter? = null, buttonText: String = stringResource(R.string.copy), showContents: Boolean = true, shareAsQrCode: Boolean = false, inBetween: (@Composable ColumnScope.() -> Unit)? = null, ) { val qrCodeSize = getQrCodeSize() - val qrPlaceHolder = if (LocalInspectionMode.current) { - QrCodeManager.makeQrCode(talerUri, qrCodeSize.value.toInt()).asImageBitmap() - } else null - val qrState = produceState(qrPlaceHolder) { - value = QrCodeManager.makeQrCode(talerUri, qrCodeSize.value.toInt()).asImageBitmap() + 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() + }}, + ).asImageBitmap() } Box( 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,6 +29,7 @@ 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.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -121,6 +122,7 @@ fun ColumnScope.PeerQrCode( QrCodeUriComposable( talerUri = talerUri, clipBoardLabel = "Push payment", + centerLogo = painterResource(R.drawable.ic_taler_qr), buttonText = stringResource(id = R.string.copy), ) { Text( diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransitionsComposable.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import net.taler.common.copyToClipBoard import net.taler.wallet.R 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,6 +22,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import net.taler.wallet.R @@ -55,6 +56,10 @@ fun PaytoQrCard( clipBoardLabel = label, showContents = true, shareAsQrCode = true, + centerLogo = when (qrCode.type) { + SPC -> painterResource(R.drawable.ic_swiss_qr) + else -> null + } ) Spacer(Modifier.height(8.dp)) diff --git a/wallet/src/main/res/drawable/ic_taler_qr.xml b/wallet/src/main/res/drawable/ic_taler_qr.xml @@ -0,0 +1,32 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="512dp" + android:height="375.49dp" + android:viewportWidth="512" + android:viewportHeight="375.49"> + <path + android:pathData="M194.29,0H317.71C425.35,0 512,72.69 512,162.98v49.54c0,90.29 -86.65,162.98 -194.29,162.98H194.29C86.65,375.49 0,302.8 0,212.51V162.98C0,72.69 86.65,0 194.29,0Z" + android:strokeLineJoin="round" + android:strokeWidth="23.4248" + android:fillColor="#ffffff" + android:strokeLineCap="round"/> + <path + android:pathData="m169.32,72.37c-52.13,8.79 -91.79,57.24 -91.79,115.51 0,64.4 48.86,116.94 108.96,116.94 60.1,0 108.96,-52.34 108.96,-116.94 0,-31.48 -11.65,-59.9 -30.46,-80.96 3.27,-3.88 12.88,-9.81 13.08,-9.4 20.44,23.71 32.91,55.61 32.91,90.36 0,73.6 -55.81,133.5 -124.5,133.5 -68.69,0 -124.5,-59.9 -124.5,-133.5 0,-73.6 55.81,-133.5 124.5,-133.5 3.48,0 11.65,0.61 11.24,0.82 -8.79,4.7 -19.83,11.04 -28.42,17.38" + android:strokeWidth="2.04437" + android:fillColor="#3047a3" + android:fillType="evenOdd"/> + <path + android:pathData="m175.86,289.89c-27.39,-24.53 -44.98,-61.13 -44.98,-102.22 0,-73.6 56.02,-133.29 124.91,-133.29 3.07,0 6.13,0.2 9.2,0.41 -9.79,4.76 -18.99,10.66 -27.39,17.58 -51.93,9.2 -91.38,57.45 -91.38,115.3 0,43.55 22.49,81.77 55.81,101.81 -8.58,1.41 -17.32,1.61 -25.96,0.61z" + android:strokeWidth="2.04437" + android:fillColor="#3047a3" + android:fillType="evenOdd"/> + <path + android:pathData="m342.68,303.8c52.13,-8.79 91.79,-57.24 91.79,-115.51 0,-64.4 -48.86,-116.94 -108.96,-116.94 -60.1,0 -108.96,52.34 -108.96,116.94 0,31.48 11.65,59.9 30.46,80.96 -3.27,3.88 -12.88,9.81 -13.08,9.4 -20.44,-23.71 -32.91,-55.61 -32.91,-90.36 0,-73.6 55.81,-133.5 124.5,-133.5 68.69,0 124.5,59.9 124.5,133.5 0,73.6 -55.81,133.5 -124.5,133.5 -3.48,0 -11.65,-0.61 -11.24,-0.82 8.79,-4.7 19.83,-11.04 28.42,-17.38" + android:strokeWidth="2.04437" + android:fillColor="#3047a3" + android:fillType="evenOdd"/> + <path + android:pathData="m336.14,86.28c27.39,24.53 44.98,61.13 44.98,102.22 0,73.6 -56.02,133.29 -124.91,133.29 -3.07,0 -6.13,-0.2 -9.2,-0.41 9.79,-4.76 18.99,-10.66 27.39,-17.58 51.93,-9.2 91.38,-57.45 91.38,-115.3 0,-43.55 -22.49,-81.77 -55.81,-101.81 8.58,-1.41 17.32,-1.61 25.96,-0.61z" + android:strokeWidth="2.04437" + android:fillColor="#3047a3" + android:fillType="evenOdd"/> +</vector>