diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt index cd39cddc60..1f190b4216 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt @@ -3,80 +3,73 @@ package chat.simplex.app.views.newchat import android.graphics.Bitmap import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.* -import androidx.compose.ui.layout.boundsInRoot -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.core.graphics.applyCanvas +import androidx.core.graphics.* +import androidx.core.graphics.drawable.toBitmap +import boofcv.alg.drawing.FiducialImageEngine import boofcv.alg.fiducial.qrcode.* import boofcv.android.ConvertBitmap import chat.simplex.app.R +import chat.simplex.app.SimplexApp import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.helpers.* import kotlinx.coroutines.launch @Composable -fun QRCode(connReq: String, modifier: Modifier = Modifier, withLogo: Boolean = true, tintColor: Color = Color(0xff062d56)) { - val view = LocalView.current +fun QRCode( + connReq: String, + modifier: Modifier = Modifier, + tintColor: Color = Color(0xff062d56), + withLogo: Boolean = true +) { val context = LocalContext.current val scope = rememberCoroutineScope() - var rect by remember { mutableStateOf(null) } - BoxWithConstraints(Modifier - .onGloballyPositioned { - rect = it.boundsInRoot() + + BoxWithConstraints { + val maxWidthInPx = with(LocalDensity.current) { maxWidth.roundToPx() } + val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) { + qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo() else it } + .asImageBitmap() } - .clickable { - scope.launch { - val r = rect - if (r != null) { - val image = Bitmap.createBitmap(r.width.toInt(), r.height.toInt(), Bitmap.Config.ARGB_8888).applyCanvas { - translate(-r.left, -r.top) - view.draw(this) - } - val file = saveTempImageUncompressed(image, false) - if (file != null) { - shareFile(context, "", file.absolutePath) + Image( + bitmap = qr, + contentDescription = stringResource(R.string.image_descr_qr_code), + modifier + .clickable { + scope.launch { + val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo() else it } + val file = saveTempImageUncompressed(image, false) + if (file != null) { + shareFile(context, "", file.absolutePath) + } } } - } - }, - contentAlignment = Alignment.Center - ) { - Image( - bitmap = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()).asImageBitmap(), - contentDescription = stringResource(R.string.image_descr_qr_code), - modifier = modifier ) - if (withLogo) { - Box( - Modifier - .size(maxWidth * 0.16f) - .background(Color.White, RoundedCornerShape(100)) - ) - Image( - painterResource(R.mipmap.icon_foreground), - null, - Modifier - .size(maxWidth * 0.24f) - ) - } } } -fun qrCodeBitmap(content: String, size: Int): Bitmap { +fun qrCodeBitmap(content: String, size: Int = 1024): Bitmap { val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() - val renderer = QrCodeGeneratorImage(5) + /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ + val numModules = QrCode.totalModules(qrCode.version) + val borderModule = 1 + // val calculatedFinalWidth = (pixelsPerModule * numModules) + 2 * (borderModule * pixelsPerModule) + // size = (x * numModules) + 2 * (borderModule * x) + // size / x = numModules + 2 * borderModule + // x = size / (numModules + 2 * borderModule) + val pixelsPerModule = size / (numModules + 2 * borderModule) + // + 1 to make it with better quality + val renderer = QrCodeGeneratorImage(pixelsPerModule + 1) + renderer.borderModule = borderModule renderer.render(qrCode) - return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565) + return ConvertBitmap.grayToBitmap(renderer.gray, Bitmap.Config.RGB_565).scale(size, size) } fun Bitmap.replaceColor(from: Int, to: Int): Bitmap { @@ -93,6 +86,17 @@ fun Bitmap.replaceColor(from: Int, to: Int): Bitmap { return this } +fun Bitmap.addLogo(): Bitmap = applyCanvas { + val radius = (width * 0.16f) / 2 + val paint = android.graphics.Paint() + paint.color = android.graphics.Color.WHITE + drawCircle(width / 2f, height / 2f, radius, paint) + val logo = SimplexApp.context.resources.getDrawable(R.mipmap.icon_foreground, null).toBitmap() + val logoSize = (width * 0.24).toInt() + translate((width - logoSize) / 2f, (height - logoSize) / 2f) + drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null) +} + @Preview @Composable fun PreviewQRCode() {