diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index 453149198b..c9054f30da 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -12,11 +12,12 @@ import SimpleXChat struct MutableQRCode: View { @Binding var uri: String + var small: Bool = false var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) var body: some View { - QRCode(uri: uri, withLogo: withLogo, tintColor: tintColor) + QRCode(uri: uri, small: small, withLogo: withLogo, tintColor: tintColor) .id("simplex-qrcode-view-for-\(uri)") } } @@ -27,7 +28,7 @@ struct SimpleXCreatedLinkQRCode: View { var onShare: (() -> Void)? = nil var body: some View { - QRCode(uri: link.simplexChatUri(short: short), onShare: onShare) + QRCode(uri: link.simplexChatUri(short: short), small: short && link.connShortLink != nil, onShare: onShare) } } @@ -38,50 +39,57 @@ struct SimpleXLinkQRCode: View { var onShare: (() -> Void)? = nil var body: some View { - QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor, onShare: onShare) + QRCode(uri: simplexChatLink(uri), small: uri.count < 200, withLogo: withLogo, tintColor: tintColor, onShare: onShare) } } +private let smallQRRatio: CGFloat = 0.63 + struct QRCode: View { let uri: String + var small: Bool = false var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) var onShare: (() -> Void)? = nil @State private var image: UIImage? = nil @State private var makeScreenshotFunc: () -> Void = {} + @State private var width: CGFloat = .infinity var body: some View { ZStack { if let image = image { - qrCodeImage(image) - GeometryReader { geo in + qrCodeImage(image).frame(width: width, height: width) + GeometryReader { g in + let w = g.size.width * (small ? smallQRRatio : 1) + let l = w * (small ? 0.195 : 0.16) + let m = w * 0.005 ZStack { if withLogo { - let w = geo.size.width Image("icon-light") .resizable() .scaledToFit() - .frame(width: w * 0.16, height: w * 0.16) - .frame(width: w * 0.165, height: w * 0.165) + .frame(width: l, height: l) + .frame(width: l + m, height: l + m) .background(.white) .clipShape(Circle()) } } .onAppear { + width = w makeScreenshotFunc = { let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale) - showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)]) + showShareSheet(items: [makeScreenshot(g.frame(in: .local).origin, size)]) onShare?() } } - .frame(width: geo.size.width, height: geo.size.height) + .frame(width: g.size.width, height: g.size.height) } } else { - Color.clear.aspectRatio(1, contentMode: .fit) + Color.clear.aspectRatio(small ? 1 / smallQRRatio : 1, contentMode: .fit) } } .onTapGesture(perform: makeScreenshotFunc) - .task { image = await generateImage(uri, tintColor: tintColor) } + .task { image = await generateImage(uri, tintColor: tintColor, errorLevel: small ? "M" : "L") } .frame(maxWidth: .infinity, maxHeight: .infinity) } } @@ -94,10 +102,11 @@ private func qrCodeImage(_ image: UIImage) -> some View { .textSelection(.enabled) } -private func generateImage(_ uri: String, tintColor: UIColor) async -> UIImage? { +private func generateImage(_ uri: String, tintColor: UIColor, errorLevel: String) async -> UIImage? { let context = CIContext() let filter = CIFilter.qrCodeGenerator() filter.message = Data(uri.utf8) + filter.correctionLevel = errorLevel if let outputImage = filter.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent) { return UIImage(cgImage: cgImage).replaceColor(UIColor.black, tintColor) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift index 17a0ffdd1c..c8cb2349e7 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift @@ -65,7 +65,7 @@ struct NewServerView: View { useServerSection(valid) if valid { Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { - MutableQRCode(uri: $serverToEdit.server) + MutableQRCode(uri: $serverToEdit.server, small: true) .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift index 13d01874ed..97bfd360cb 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift @@ -110,7 +110,7 @@ struct ProtocolServerView: View { useServerSection(valid) if valid { Section(header: Text("Add to another device").foregroundColor(theme.colors.secondary)) { - MutableQRCode(uri: $serverToEdit.server) + MutableQRCode(uri: $serverToEdit.server, small: true) .listRowInsets(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)) } } diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 759a4c79f4..a6025879eb 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -3797,7 +3797,7 @@ alert button */ "Or securely share this file link" = "Или передайте эту ссылку"; /* No comment provided by engineer. */ -"Or show this code" = "Или покажите этот код"; +"Or show this code" = "Или покажите код"; /* No comment provided by engineer. */ "Or to share privately" = "Или поделиться конфиденциально"; @@ -4844,7 +4844,7 @@ chat item action */ "Share SimpleX address on social media." = "Поделитесь SimpleX адресом в социальных сетях."; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Поделиться одноразовой ссылкой-приглашением"; +"Share this 1-time invite link" = "Поделитесь одноразовой ссылкой"; /* No comment provided by engineer. */ "Share to SimpleX" = "Поделиться в SimpleX"; diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt index fc323f6ffd..4f47fda130 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt @@ -101,13 +101,13 @@ actual fun GrayU8.toImageBitmap(): ImageBitmap = ConvertBitmap.grayToBitmap(this actual fun ImageBitmap.hasAlpha(): Boolean = hasAlpha -actual fun ImageBitmap.addLogo(): ImageBitmap = asAndroidBitmap().applyCanvas { - val radius = (width * 0.16f) / 2 +actual fun ImageBitmap.addLogo(size: Float): ImageBitmap = asAndroidBitmap().applyCanvas { + val radius = (width * size) / 2 val paint = android.graphics.Paint() paint.color = android.graphics.Color.WHITE drawCircle(width / 2f, height / 2f, radius, paint) val logo = androidAppContext.resources.getDrawable(R.drawable.icon_foreground_android_common, null).toBitmap() - val logoSize = (width * 0.24).toInt() + val logoSize = (width * size * 1.5).toInt() translate((width - logoSize) / 2f, (height - logoSize) / 2f) drawBitmap(logo, null, android.graphics.Rect(0, 0, logoSize, logoSize), null) }.asImageBitmap() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt index fca69d5398..19e40ab0a2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Images.kt @@ -16,7 +16,7 @@ expect fun compressImageData(bitmap: ImageBitmap, usePng: Boolean): ByteArrayOut expect fun GrayU8.toImageBitmap(): ImageBitmap expect fun ImageBitmap.hasAlpha(): Boolean -expect fun ImageBitmap.addLogo(): ImageBitmap +expect fun ImageBitmap.addLogo(size: Float): ImageBitmap expect fun ImageBitmap.scale(width: Int, height: Int): ImageBitmap expect fun isImage(uri: URI): Boolean diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index bacb5ab802..0ed32f845b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -31,6 +31,7 @@ fun SimpleXCreatedLinkQRCode( ) { QRCode( connLink.simplexChatUri(short), + small = short && connLink.connShortLink != null, modifier, padding, tintColor, @@ -50,6 +51,7 @@ fun SimpleXLinkQRCode( ) { QRCode( simplexChatLink(connReq), + small = connReq.count() < 200, modifier, padding, tintColor, @@ -61,6 +63,7 @@ fun SimpleXLinkQRCode( @Composable fun QRCode( connReq: String, + small: Boolean = false, modifier: Modifier = Modifier, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF), tintColor: Color = Color(0xff062d56), @@ -68,9 +71,11 @@ fun QRCode( onShare: (() -> Unit)? = null, ) { val scope = rememberCoroutineScope() + val logoSize = if (small) 0.21f else 0.16f + val errorLevel = if (small) QrCode.ErrorLevel.M else QrCode.ErrorLevel.L val qr = remember(connReq, tintColor, withLogo) { - qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } + qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo(logoSize) else it } } Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Image( @@ -79,12 +84,13 @@ fun QRCode( Modifier .padding(padding) .widthIn(max = 400.dp) + .fillMaxWidth(if (small) 0.67f else 1f) .aspectRatio(1f) .then(modifier) .clickable { scope.launch { - val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) - .let { if (withLogo) it.addLogo() else it } + val image = qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + .let { if (withLogo) it.addLogo(logoSize) else it } val file = saveTempImageUncompressed(image, true) if (file != null) { shareFile("", CryptoFile.plain(file.absolutePath)) @@ -96,8 +102,8 @@ fun QRCode( } } -fun qrCodeBitmap(content: String, size: Int = 1024): ImageBitmap { - val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate() +fun qrCodeBitmap(content: String, size: Int = 1024, errorLevel: QrCode.ErrorLevel): ImageBitmap { + val qrCode = QrCodeEncoder().addAutomatic(content).setError(errorLevel).fixate() /** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */ val numModules = QrCode.totalModules(qrCode.version) // Hide border on light themes to make it fit to the same place as camera in QRCodeScanner. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt index bebc96a28c..8626fd3143 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServerView.kt @@ -189,7 +189,7 @@ fun CustomServer( if (valid.value) { SectionDividerSpaced() SectionView(stringResource(MR.strings.smp_servers_add_to_another_device).uppercase()) { - QRCode(serverAddress.value) + QRCode(serverAddress.value, small = true) } } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index 53f3301507..d0ba082adf 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -133,9 +133,9 @@ actual fun ImageBitmap.hasAlpha(): Boolean { return false } -actual fun ImageBitmap.addLogo(): ImageBitmap { - val radius = (width * 0.16f).toInt() - val logoSize = (width * 0.24).toInt() +actual fun ImageBitmap.addLogo(size: Float): ImageBitmap { + val radius = (width * size).toInt() + val logoSize = (width * size * 1.5).toInt() val logo: BufferedImage = MR.images.icon_foreground_common.image val original = toAwtImage() val withLogo = BufferedImage(width, height, original.type)