taler-android

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

commit f7f4133493878258740a7d016a5fef801458df95
parent 24258708e389e9452f88e9dd34bd79f8655acefa
Author: Iván Ávalos <avalos@disroot.org>
Date:   Tue, 17 Mar 2026 12:30:26 +0100

[wallet] new animated Taler QR code

Diffstat:
Mmerchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt | 6------
Mtaler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt | 4++--
Mtaler-kotlin-android/src/main/java/net/taler/common/QrCodeManager.kt | 6+++---
Mtaler-kotlin-android/src/main/java/net/taler/lib/android/AnimatedQrCodeComposable.kt | 152+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mwallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt | 9+++++----
Mwallet/src/main/java/net/taler/wallet/compose/QrCodeUriComposable.kt | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mwallet/src/main/java/net/taler/wallet/compose/ShareButton.kt | 3+++
Mwallet/src/main/java/net/taler/wallet/donau/DonauStatementComposable.kt | 6++++--
Mwallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt | 14++++----------
Mwallet/src/main/java/net/taler/wallet/transfer/PaytoQrCard.kt | 20++++++++++++++------
Dwallet/src/main/res/drawable/ic_taler_qr.xml | 32--------------------------------
11 files changed, 169 insertions(+), 164 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 @@ -198,15 +198,9 @@ class ProcessPaymentFragment : Fragment() { val blockSize = minOf(ui.qrcodeView.width, ui.qrcodeView.height).coerceAtLeast(256) val qrSize = (blockSize * 0.88f).toInt().coerceAtLeast(256) currentQrBitmap = makePaymentQrCode(text, qrSize) - - val density = resources.displayMetrics.density - val widthDp = ui.qrcodeView.width / density - val heightDp = ui.qrcodeView.height / density ui.qrcodeView.setContent { PosTheme { AnimatedQrCodeComposable( - width = widthDp.dp, - height = heightDp.dp, link = text, logoPainter = painterResource(R.drawable.ic_taler_logo_qr), modifier = Modifier.fillMaxSize(), diff --git a/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt @@ -234,8 +234,8 @@ const val SHARE_QR_QUALITY = 90 * NOTE: make sure to properly setup file provider * https://developer.android.com/training/secure-file-sharing/setup-sharing */ -suspend fun String.shareAsQrCode(context: Context, authority: String) { - val qrBitmap = QrCodeManager.makeQrCode(this, SHARE_QR_SIZE) +suspend fun String.shareAsQrCode(context: Context, authority: String, qrBitmap: Bitmap? = null) { + val qrBitmap = qrBitmap ?: QrCodeManager.makeQrCode(this, SHARE_QR_SIZE) val outputDir = context.cacheDir try { val uri = withContext(Dispatchers.IO) { 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 @@ -50,8 +50,8 @@ object QrCodeManager { margin: Int = 2, errorCorrection: ErrorCorrectionLevel = ErrorCorrectionLevel.M, centerLogo: Drawable? = null, - centerLogoSize: QrLogoSize = QrLogoSize.MEDIUM, - drawBackground: Boolean = false, + centerLogoSize: QrLogoSize? = QrLogoSize.MEDIUM, + drawBackground: Boolean? = false, darkColor: Int = BLACK, lightColor: Int = WHITE, trimQuietZone: Boolean = false, @@ -73,7 +73,7 @@ object QrCodeManager { val qrBitmap = if (trimQuietZone) trimQrQuietZone(bmp, lightColor) else bmp - return if (centerLogo != null) { + return 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 @@ -30,15 +30,17 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.requiredSize 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.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -51,49 +53,22 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import net.taler.common.QrCodeManager.makeQrCode +const val QR_CORNER_RADIUS = 0.08f +const val QR_STRIPE_WIDTH = 0.025f +const val QR_DATA_SIZE = 0.88f +const val QR_LOGO_SIZE = 0.30f * QR_DATA_SIZE + @Composable fun AnimatedQrCodeComposable( - width: Dp, - height: Dp, + modifier: Modifier = Modifier, link: String, logoPainter: Painter? = null, - qrCornerRadiusFraction: Float = 0f, - modifier: Modifier = Modifier, + qrCornerRadiusFraction: Float = QR_CORNER_RADIUS, ) { - val blockSize = minOf(width, height) - val cornerRadius = blockSize * 0.08f - val stripeWidth = blockSize * 0.025f - val qrSize = blockSize * 0.88f - val logoWidth = qrSize * 0.30f - val qrCornerRadius = qrSize * qrCornerRadiusFraction.coerceIn(0f, 0.5f) - val qrImageModifier = if (qrCornerRadius > 0.dp) { - Modifier - .size(qrSize) - .background(Color.White) - .clip(RoundedCornerShape(qrCornerRadius)) - } else { - Modifier - .size(qrSize) - .background(Color.White) - } - - 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, - ) - } - val infinite = rememberInfiniteTransition(label = "qrStripe") val angle by infinite.animateFloat( initialValue = 0f, @@ -135,59 +110,82 @@ fun AnimatedQrCodeComposable( } Box( - modifier = modifier.requiredSize(width, height), + modifier = modifier + .fillMaxSize() + .aspectRatio(1f), contentAlignment = Alignment.Center, ) { + val density = LocalDensity.current + var drawSize by remember { mutableStateOf<Dp?>(null) } + Box( - modifier = Modifier.size(blockSize), - contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape( + percent = (qrCornerRadiusFraction * 100).toInt())) + .zIndex(-1f) + .background(Color.White), //TODO: VLADA design MaterialTheme.colorScheme.surfaceVariant), + ) + + Canvas( + modifier = Modifier.fillMaxSize(), ) { - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(cornerRadius)) - .background(Color.White), //TODO: VLADA design MaterialTheme.colorScheme.surfaceVariant), - ) + val s = with(density) { size.width.toDp() } + drawSize = s + + val cornerRadius = s * qrCornerRadiusFraction + val stripeWidth = s * QR_STRIPE_WIDTH + val stripePx = stripeWidth.toPx() + val inset = stripePx / 2f + val cornerPx = (cornerRadius.toPx() - inset).coerceAtLeast(0f) + val cx = size.width / 2f + val cy = size.height / 2f - Canvas( - modifier = Modifier.fillMaxSize(), - ) { - val stripePx = stripeWidth.toPx() - val inset = stripePx / 2f - val cornerPx = (cornerRadius.toPx() - inset).coerceAtLeast(0f) - val cx = size.width / 2f - val cy = size.height / 2f + val shader = SweepGradient(cx, cy, gradientColors, gradientStops) + gradientMatrix.reset() + gradientMatrix.setRotate(angle, cx, cy) + shader.setLocalMatrix(gradientMatrix) - val shader = SweepGradient(cx, cy, gradientColors, gradientStops) - gradientMatrix.reset() - gradientMatrix.setRotate(angle, cx, cy) - shader.setLocalMatrix(gradientMatrix) + stripePaint.strokeWidth = stripePx + stripePaint.shader = shader + + drawContext.canvas.nativeCanvas.drawRoundRect( + RectF(inset, inset, size.width - inset, size.height - inset), + cornerPx, + cornerPx, + stripePaint, + ) + } - stripePaint.strokeWidth = stripePx - stripePaint.shader = shader + 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, + ) + } - drawContext.canvas.nativeCanvas.drawRoundRect( - RectF(inset, inset, size.width - inset, size.height - inset), - cornerPx, - cornerPx, - stripePaint, - ) - } + Image( + bitmap = qrBitmap.asImageBitmap(), + contentDescription = null, + contentScale = ContentScale.FillBounds, + modifier = Modifier.size(qrSize).background(Color.White), + ) + if (logoPainter != null) { Image( - bitmap = qrBitmap.asImageBitmap(), + painter = logoPainter, contentDescription = null, - contentScale = ContentScale.FillBounds, - modifier = qrImageModifier, + modifier = Modifier.width(logoWidth), ) - - if (logoPainter != null) { - Image( - painter = logoPainter, - contentDescription = null, - modifier = Modifier.width(logoWidth), - ) - } } } } diff --git a/wallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt b/wallet/src/main/java/net/taler/wallet/compose/ExpandableCard.kt @@ -44,7 +44,6 @@ 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 @@ -154,9 +153,11 @@ fun ExpandableCardPreview( header = { Text("Swiss QR") }, content = { QrCodeUriComposable( - talerUri = "taler://withdraw-exchange", - clipBoardLabel = "", - centerLogo = ContextCompat.getDrawable(context, R.drawable.ic_swiss_qr), + qrData = "taler://withdraw-exchange", + clipboardLabel = "", + params = QrCodeParams.Custom( + 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.Bitmap import android.graphics.drawable.Drawable import androidx.compose.foundation.Image import androidx.compose.foundation.horizontalScroll @@ -40,45 +41,68 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.min +import androidx.core.content.ContextCompat import net.taler.common.QrCodeManager import net.taler.common.QrLogoSize import net.taler.common.copyToClipBoard +import net.taler.lib.android.AnimatedQrCodeComposable import net.taler.wallet.R +sealed class QrCodeParams { + data object Taler: QrCodeParams() + + data class Custom( + val centerLogo: Drawable? = null, + val centerLogoSize: QrLogoSize = QrLogoSize.MEDIUM, + val drawCenterLogoBackground: Boolean = false, + ): QrCodeParams() +} + @Composable fun ColumnScope.QrCodeUriComposable( modifier: Modifier = Modifier, - talerUri: String, - clipBoardLabel: String, - centerLogo: Drawable? = null, - centerLogoSize: QrLogoSize = QrLogoSize.MEDIUM, - drawCenterLogoBackground: Boolean = false, + qrData: String, + clipboardLabel: String, + params: QrCodeParams, buttonText: String = stringResource(R.string.copy), showContents: Boolean = true, shareAsQrCode: Boolean = false, inBetween: (@Composable ColumnScope.() -> Unit)? = null, ) { + val context = LocalContext.current val qrCodeSize = getQrCodeSize() - val qrState = produceState<ImageBitmap?>(null) { + val qrState by produceState<Bitmap?>(null) { value = QrCodeManager.makeQrCode( - talerUri, + qrData, qrCodeSize.value.toInt(), - centerLogo = centerLogo, - centerLogoSize = centerLogoSize, - drawBackground = drawCenterLogoBackground, - ).asImageBitmap() + centerLogo = when (params) { + QrCodeParams.Taler -> ContextCompat.getDrawable(context, net.taler.common.R.drawable.ic_taler_logo_qr) + is QrCodeParams.Custom -> params.centerLogo + }, + + centerLogoSize = when (params) { + QrCodeParams.Taler -> QrLogoSize.MEDIUM + is QrCodeParams.Custom -> params.centerLogoSize + }, + + drawBackground = when (params) { + QrCodeParams.Taler -> true + is QrCodeParams.Custom -> false + } + ) } Box( @@ -88,12 +112,24 @@ fun ColumnScope.QrCodeUriComposable( .padding(bottom = if (showContents) 8.dp else 0.dp), contentAlignment = Alignment.Center, ) { - qrState.value?.let { qrCode -> - Image( - modifier = Modifier.fillMaxSize(), - bitmap = qrCode, - contentDescription = stringResource(id = R.string.button_scan_qr_code), - ) + when (params) { + QrCodeParams.Taler -> { + AnimatedQrCodeComposable( + modifier = Modifier.fillMaxSize(), + link = qrData, + logoPainter = painterResource(net.taler.common.R.drawable.ic_taler_logo_qr) + ) + } + + is QrCodeParams.Custom -> { + qrState?.let { qrCode -> + Image( + modifier = Modifier.fillMaxSize(), + bitmap = qrCode.asImageBitmap(), + contentDescription = null, + ) + } + } } } @@ -110,7 +146,7 @@ fun ColumnScope.QrCodeUriComposable( .horizontalScroll(scrollState), fontFamily = FontFamily.Monospace, style = MaterialTheme.typography.bodyMedium, - text = talerUri, + text = qrData, ) } } @@ -123,8 +159,8 @@ fun ColumnScope.QrCodeUriComposable( ) { if (!shareAsQrCode) { CopyToClipboardButton( - label = clipBoardLabel, - content = talerUri, + label = clipboardLabel, + content = qrData, buttonText = buttonText, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primaryContainer, @@ -134,8 +170,9 @@ fun ColumnScope.QrCodeUriComposable( } ShareButton( - content = talerUri, + content = qrData, shareAsQrCode = shareAsQrCode, + qrBitmap = qrState, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer diff --git a/wallet/src/main/java/net/taler/wallet/compose/ShareButton.kt b/wallet/src/main/java/net/taler/wallet/compose/ShareButton.kt @@ -19,6 +19,7 @@ package net.taler.wallet.compose import android.content.Intent import android.content.Intent.ACTION_SEND import android.content.Intent.EXTRA_TEXT +import android.graphics.Bitmap import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons @@ -46,6 +47,7 @@ fun ShareButton( buttonText: String = stringResource(R.string.share), colors: ButtonColors = ButtonDefaults.buttonColors(), shareAsQrCode: Boolean = false, + qrBitmap: Bitmap? = null, ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -57,6 +59,7 @@ fun ShareButton( scope.launch { content.shareAsQrCode( context, "${BuildConfig.APPLICATION_ID}.fileprovider", + qrBitmap, ) } } else { val sendIntent: Intent = Intent().apply { diff --git a/wallet/src/main/java/net/taler/wallet/donau/DonauStatementComposable.kt b/wallet/src/main/java/net/taler/wallet/donau/DonauStatementComposable.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.unit.dp import net.taler.common.Amount import net.taler.wallet.BottomInsetsSpacer import net.taler.wallet.R +import net.taler.wallet.compose.QrCodeParams import net.taler.wallet.compose.QrCodeUriComposable import net.taler.wallet.compose.TalerSurface import net.taler.wallet.transactions.AmountType @@ -86,9 +87,10 @@ fun DonauStatementComposable( ) QrCodeUriComposable( - talerUri = statement.uri, - clipBoardLabel = "Donau", + qrData = statement.uri, + clipboardLabel = "Donau", buttonText = stringResource(id = R.string.copy), + params = QrCodeParams.Taler, shareAsQrCode = true, ) diff --git a/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt b/wallet/src/main/java/net/taler/wallet/peer/TransactionPeerPushDebit.kt @@ -28,18 +28,16 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext 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.QrLogoSize import net.taler.common.Timestamp import net.taler.wallet.R import net.taler.wallet.balances.ScopeInfo +import net.taler.wallet.compose.QrCodeParams import net.taler.wallet.compose.QrCodeUriComposable import net.taler.wallet.compose.TalerSurface import net.taler.wallet.transactions.AmountType @@ -105,8 +103,6 @@ fun ColumnScope.PeerQrCode( talerUri: String?, instructionResId: Int, ) { - val context = LocalContext.current - if (state == TransactionState(Pending) && state.minor != KycRequired) { Text( modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp), @@ -119,11 +115,9 @@ fun ColumnScope.PeerQrCode( Spacer(Modifier.height(8.dp)) QrCodeUriComposable( modifier = Modifier.padding(horizontal = 16.dp), - talerUri = talerUri, - clipBoardLabel = "Push payment", - centerLogo = ContextCompat.getDrawable(context, R.drawable.ic_taler_logo_qr), - centerLogoSize = QrLogoSize.BIG, - drawCenterLogoBackground = true, + qrData = talerUri, + clipboardLabel = "Push payment", + params = QrCodeParams.Taler, 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 @@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat import net.taler.common.QrLogoSize import net.taler.wallet.R import net.taler.wallet.compose.ExpandableCard +import net.taler.wallet.compose.QrCodeParams import net.taler.wallet.compose.QrCodeUriComposable import net.taler.wallet.withdraw.QrCodeSpec import net.taler.wallet.withdraw.QrCodeSpec.Type.EpcQr @@ -43,14 +44,21 @@ fun ColumnScope.PaytoQrCode( val context = LocalContext.current QrCodeUriComposable( modifier = modifier, - talerUri = qrCode.qrContent, - clipBoardLabel = getQrCodeLabel(qrCode), + qrData = qrCode.qrContent, + clipboardLabel = getQrCodeLabel(qrCode), showContents = true, shareAsQrCode = true, - centerLogoSize = QrLogoSize.SMALL, - centerLogo = when (qrCode.type) { - SPC -> ContextCompat.getDrawable(context, R.drawable.ic_swiss_qr) - else -> null + params = when(qrCode.type) { + SPC -> QrCodeParams.Custom( + centerLogoSize = QrLogoSize.SMALL, + centerLogo = when (qrCode.type) { + SPC -> ContextCompat.getDrawable(context, R.drawable.ic_swiss_qr) + else -> null + }, + ) + + EpcQr -> QrCodeParams.Custom() + else -> QrCodeParams.Custom() }, ) } diff --git a/wallet/src/main/res/drawable/ic_taler_qr.xml b/wallet/src/main/res/drawable/ic_taler_qr.xml @@ -1,32 +0,0 @@ -<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>