QrCodeUriComposable.kt (7531B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2022 Taler Systems S.A. 4 * 5 * GNU Taler is free software; you can redistribute it and/or modify it under the 6 * terms of the GNU General Public License as published by the Free Software 7 * Foundation; either version 3, or (at your option) any later version. 8 * 9 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 package net.taler.wallet.compose 18 19 import android.graphics.Bitmap 20 import android.graphics.drawable.Drawable 21 import androidx.compose.foundation.Image 22 import androidx.compose.foundation.horizontalScroll 23 import androidx.compose.foundation.layout.Arrangement 24 import androidx.compose.foundation.layout.Box 25 import androidx.compose.foundation.layout.ColumnScope 26 import androidx.compose.foundation.layout.Row 27 import androidx.compose.foundation.layout.Spacer 28 import androidx.compose.foundation.layout.aspectRatio 29 import androidx.compose.foundation.layout.fillMaxSize 30 import androidx.compose.foundation.layout.fillMaxWidth 31 import androidx.compose.foundation.layout.padding 32 import androidx.compose.foundation.layout.size 33 import androidx.compose.foundation.rememberScrollState 34 import androidx.compose.material.icons.Icons 35 import androidx.compose.material.icons.filled.ContentCopy 36 import androidx.compose.material3.Button 37 import androidx.compose.material3.ButtonColors 38 import androidx.compose.material3.ButtonDefaults 39 import androidx.compose.material3.Card 40 import androidx.compose.material3.Icon 41 import androidx.compose.material3.MaterialTheme 42 import androidx.compose.material3.Text 43 import androidx.compose.runtime.Composable 44 import androidx.compose.runtime.getValue 45 import androidx.compose.runtime.produceState 46 import androidx.compose.ui.Alignment 47 import androidx.compose.ui.Modifier 48 import androidx.compose.ui.graphics.asImageBitmap 49 import androidx.compose.ui.platform.LocalConfiguration 50 import androidx.compose.ui.platform.LocalContext 51 import androidx.compose.ui.res.painterResource 52 import androidx.compose.ui.res.stringResource 53 import androidx.compose.ui.text.font.FontFamily 54 import androidx.compose.ui.unit.Dp 55 import androidx.compose.ui.unit.dp 56 import androidx.compose.ui.unit.min 57 import androidx.core.content.ContextCompat 58 import net.taler.lib.android.QrCodeManager 59 import net.taler.lib.android.QrLogoSize 60 import net.taler.lib.android.copyToClipBoard 61 import net.taler.lib.android.AnimatedQrCodeComposable 62 import net.taler.wallet.R 63 64 sealed class QrCodeParams { 65 data object Taler: QrCodeParams() 66 67 data class Custom( 68 val centerLogo: Drawable? = null, 69 val centerLogoSize: QrLogoSize = QrLogoSize.MEDIUM, 70 val drawCenterLogoBackground: Boolean = false, 71 ): QrCodeParams() 72 } 73 74 @Composable 75 fun ColumnScope.QrCodeUriComposable( 76 modifier: Modifier = Modifier, 77 qrData: String, 78 clipboardLabel: String, 79 params: QrCodeParams, 80 buttonText: String = stringResource(R.string.copy), 81 showContents: Boolean = true, 82 shareAsQrCode: Boolean = false, 83 inBetween: (@Composable ColumnScope.() -> Unit)? = null, 84 ) { 85 val context = LocalContext.current 86 val qrCodeSize = getQrCodeSize() 87 val qrState by produceState<Bitmap?>(null) { 88 value = QrCodeManager.makeQrCode( 89 qrData, 90 qrCodeSize.value.toInt(), 91 centerLogo = when (params) { 92 QrCodeParams.Taler -> ContextCompat.getDrawable(context, net.taler.common.R.drawable.ic_taler_logo_qr) 93 is QrCodeParams.Custom -> params.centerLogo 94 }, 95 96 centerLogoSize = when (params) { 97 QrCodeParams.Taler -> QrLogoSize.MEDIUM 98 is QrCodeParams.Custom -> params.centerLogoSize 99 }, 100 101 drawBackground = false, 102 ) 103 } 104 105 Box( 106 modifier 107 .fillMaxWidth() 108 .aspectRatio(1f) 109 .padding(bottom = if (showContents) 8.dp else 0.dp), 110 contentAlignment = Alignment.Center, 111 ) { 112 when (params) { 113 QrCodeParams.Taler -> { 114 AnimatedQrCodeComposable( 115 modifier = Modifier.fillMaxSize(), 116 link = qrData, 117 logoPainter = painterResource(net.taler.common.R.drawable.ic_taler_logo_qr) 118 ) 119 } 120 121 is QrCodeParams.Custom -> { 122 qrState?.let { qrCode -> 123 Image( 124 modifier = Modifier.fillMaxSize(), 125 bitmap = qrCode.asImageBitmap(), 126 contentDescription = null, 127 ) 128 } 129 } 130 } 131 } 132 133 if (inBetween != null) inBetween() 134 val scrollState = rememberScrollState() 135 if (showContents) { 136 if (!shareAsQrCode) { 137 Card(modifier = Modifier 138 .padding(horizontal = 16.dp) 139 .padding(bottom = 16.dp, top = 10.dp)) { 140 Text( 141 modifier = Modifier 142 .padding(6.dp) 143 .horizontalScroll(scrollState), 144 fontFamily = FontFamily.Monospace, 145 style = MaterialTheme.typography.bodyMedium, 146 text = qrData, 147 ) 148 } 149 } 150 151 Row( 152 modifier = Modifier 153 .padding(horizontal = 16.dp) 154 .fillMaxWidth(), 155 horizontalArrangement = Arrangement.SpaceEvenly, 156 ) { 157 if (!shareAsQrCode) { 158 CopyToClipboardButton( 159 label = clipboardLabel, 160 content = qrData, 161 buttonText = buttonText, 162 colors = ButtonDefaults.buttonColors( 163 containerColor = MaterialTheme.colorScheme.primaryContainer, 164 contentColor = MaterialTheme.colorScheme.onPrimaryContainer 165 ) 166 ) 167 } 168 169 ShareButton( 170 content = qrData, 171 shareAsQrCode = shareAsQrCode, 172 qrBitmap = qrState, 173 colors = ButtonDefaults.buttonColors( 174 containerColor = MaterialTheme.colorScheme.primaryContainer, 175 contentColor = MaterialTheme.colorScheme.onPrimaryContainer 176 ), 177 ) 178 } 179 } 180 } 181 182 @Composable 183 fun getQrCodeSize(): Dp { 184 val configuration = LocalConfiguration.current 185 val screenHeight = configuration.screenHeightDp.dp 186 val screenWidth = configuration.screenWidthDp.dp 187 return min(screenHeight, screenWidth) 188 } 189 190 @Composable 191 fun CopyToClipboardButton( 192 label: String, 193 content: String, 194 modifier: Modifier = Modifier, 195 buttonText: String = stringResource(R.string.copy), 196 colors: ButtonColors = ButtonDefaults.buttonColors(), 197 ) { 198 val context = LocalContext.current 199 Button( 200 modifier = modifier, 201 colors = colors, 202 onClick = { copyToClipBoard(context, label, content) }, 203 ) { 204 Icon( 205 Icons.Default.ContentCopy, 206 buttonText, 207 modifier = Modifier.size(ButtonDefaults.IconSize), 208 ) 209 Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 210 Text(buttonText) 211 } 212 }