From 8a445ece90f563604abf58e613cbd3b50c083514 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 6 Feb 2023 11:45:56 +0300 Subject: [PATCH] ios: colored and clickable qr code with logo (#1885) * ios: colored and clickable qr code with logo * size of circle * same padding as in android * add padding to logo --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- apps/ios/Shared/Model/ImageUtils.swift | 50 +++++++++++++++++++++- apps/ios/Shared/Views/NewChat/QRCode.swift | 43 ++++++++++++++++++- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Model/ImageUtils.swift b/apps/ios/Shared/Model/ImageUtils.swift index 92be827dfc..48769a8390 100644 --- a/apps/ios/Shared/Model/ImageUtils.swift +++ b/apps/ios/Shared/Model/ImageUtils.swift @@ -133,7 +133,7 @@ func imageHasAlpha(_ img: UIImage) -> Bool { context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)) if let data = context.data { let data = data.assumingMemoryBound(to: UInt8.self) - let size = cgImage.width * cgImage.height + let size = cgImage.width * cgImage.height * 4 var i = 0 while i < size { if data[i] < 255 { return true } @@ -203,3 +203,51 @@ func dropImagePrefix(_ s: String) -> String { private func dropPrefix(_ s: String, _ prefix: String) -> String { s.hasPrefix(prefix) ? String(s.dropFirst(prefix.count)) : s } + +extension UIImage { + func replaceColor(_ from: UIColor, _ to: UIColor) -> UIImage { + if let cgImage = cgImage { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue) + if let context = CGContext(data: nil, width: cgImage.width, height: cgImage.height, bitsPerComponent: 8, bytesPerRow: cgImage.width * 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) { + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)) + if let data = context.data { + var fromAlpha: CGFloat = 0 + var fromRed: CGFloat = 0 + var fromGreen: CGFloat = 0 + var fromBlue: CGFloat = 0 + var toAlpha: CGFloat = 0 + var toRed: CGFloat = 0 + var toGreen: CGFloat = 0 + var toBlue: CGFloat = 0 + from.getRed(&fromRed, green: &fromGreen, blue: &fromBlue, alpha: &fromAlpha) + to.getRed(&toRed, green: &toGreen, blue: &toBlue, alpha: &toAlpha) + let fAlpha = UInt8(UInt8(fromAlpha * 255)) + let fRed = UInt8(fromRed * 255) + let fGreen = UInt8(fromGreen * 255) + let fBlue = UInt8(fromBlue * 255) + let tAlpha = UInt8(toAlpha * 255) + let tRed = UInt8(toRed * 255) + let tGreen = UInt8(toGreen * 255) + let tBlue = UInt8(toBlue * 255) + let data = data.assumingMemoryBound(to: UInt8.self) + let size = cgImage.width * cgImage.height * 4 + var i = 0 + while i < size { + if data[i] == fAlpha && data[i + 1] == fRed && data[i + 2] == fGreen && data[i + 3] == fBlue { + data[i + 0] = tAlpha + data[i + 1] = tRed + data[i + 2] = tGreen + data[i + 3] = tBlue + } + i += 4 + } + } + if let img = context.makeImage() { + return UIImage(cgImage: img) + } + } + } + return self + } +} \ No newline at end of file diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index 27f014fe5a..e60a0c5362 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -30,17 +30,42 @@ struct MutableQRCode: View { struct QRCode: View { let uri: String - @State private var image: UIImage? + var withLogo: Bool = true + var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) + @State private var image: UIImage? = nil + @State private var makeScreenshotBinding: () -> Void = {} var body: some View { ZStack { if let image = image { qrCodeImage(image) } + GeometryReader { geo in + 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) + .background(.white) + .clipShape(Circle()) + } + } + .onAppear { + makeScreenshotBinding = { + showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, geo.size)]) + } + } + .frame(width: geo.size.width, height: geo.size.height) + } } + .onTapGesture(perform: makeScreenshotBinding) .onAppear { - image = image ?? generateImage(uri) + image = image ?? generateImage(uri)?.replaceColor(UIColor.black, tintColor) } + } } @@ -63,6 +88,20 @@ private func generateImage(_ uri: String) -> UIImage? { return nil } +extension View { + func makeScreenshot(_ origin: CGPoint? = nil, _ targetSize: CGSize? = nil) -> UIImage { + let controller = UIHostingController(rootView: self.edgesIgnoringSafeArea(.all)) + let targetSize = targetSize ?? controller.view.intrinsicContentSize + let view = controller.view + view?.bounds = CGRect(origin: origin ?? .zero, size: targetSize) + view?.backgroundColor = .clear + let renderer = UIGraphicsImageRenderer(size: targetSize) + return renderer.image { _ in + view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) + } + } +} + struct QRCode_Previews: PreviewProvider { static var previews: some View { QRCode(uri: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D")