diff --git a/apps/ios/Shared/Views/ChatList/ChatHelp.swift b/apps/ios/Shared/Views/ChatList/ChatHelp.swift index 0001d609c6..7c6fe94ac6 100644 --- a/apps/ios/Shared/Views/ChatList/ChatHelp.swift +++ b/apps/ios/Shared/Views/ChatList/ChatHelp.swift @@ -39,7 +39,7 @@ struct ChatHelp: View { HStack(spacing: 8) { Text("Tap button ") - NewChatSheetButton(showNewChatSheet: $showNewChatSheet) + NewChatInviteButton(showNewChatSheet: $showNewChatSheet) Text("above, then choose:") } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index b536b5eb68..1d5a2f0e57 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -128,7 +128,7 @@ struct ChatListView: View { case .some(true): HStack { createGroupButton() - NewChatSheetButton(showNewChatSheet: $showNewChatSheet) + NewChatInviteButton(showNewChatSheet: $showNewChatSheet) } case .some(false): chatStoppedIcon() case .none: EmptyView() @@ -307,28 +307,13 @@ struct ChatListSearchBar: View { .transition(.identity) } - scanCodeButton() + NewChatScanButton() } Divider() } .padding(.horizontal, 18) .padding(.vertical, 2) } - - private func scanCodeButton() -> some View { - Button { - showScanCodeSheet = true - } label: { - Image(systemName: "qrcode") - .resizable() - .scaledToFit() - .foregroundColor(.accentColor) - .frame(width: 20, height: 20) - } - .sheet(isPresented: $showScanCodeSheet) { - NewChatView(selection: .connect, showScanQRCodeSheet: true) - } - } } func chatStoppedIcon() -> some View { diff --git a/apps/ios/Shared/Views/NewChat/NewChatSheetButton.swift b/apps/ios/Shared/Views/NewChat/NewChatInviteButton.swift similarity index 84% rename from apps/ios/Shared/Views/NewChat/NewChatSheetButton.swift rename to apps/ios/Shared/Views/NewChat/NewChatInviteButton.swift index 201326abc1..98ec215eea 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatSheetButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatInviteButton.swift @@ -1,5 +1,5 @@ // -// NewChatSheetButton.swift +// NewChatInviteButton.swift // SimpleX (iOS) // // Created by spaced4ndy on 28.11.2023. @@ -7,9 +7,8 @@ // import SwiftUI -import SimpleXChat -struct NewChatSheetButton: View { +struct NewChatInviteButton: View { @Binding var showNewChatSheet: Bool var body: some View { @@ -28,5 +27,5 @@ struct NewChatSheetButton: View { } //#Preview { -// NewChatSheetButton() +// NewChatInviteButton() //} diff --git a/apps/ios/Shared/Views/NewChat/NewChatScanButton.swift b/apps/ios/Shared/Views/NewChat/NewChatScanButton.swift new file mode 100644 index 0000000000..f4c676e0af --- /dev/null +++ b/apps/ios/Shared/Views/NewChat/NewChatScanButton.swift @@ -0,0 +1,32 @@ +// +// NewChatScanButton.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 30.11.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct NewChatScanButton: View { + @State private var showNewChatSheet = false + + var body: some View { + Button { + showNewChatSheet = true + } label: { + Image(systemName: "qrcode") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + } + .sheet(isPresented: $showNewChatSheet) { + NewChatView(selection: .connect, showQRCodeScanner: true) + .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) // fixes .refreshable in ChatListView affecting nested view + } + } +} + +//#Preview { +// NewChatScanButton() +//} diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index 3beea1bf1e..c9903c9d71 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -30,7 +30,7 @@ enum NewChatOption: Identifiable { struct NewChatView: View { @EnvironmentObject var m: ChatModel @State var selection: NewChatOption - @State var showScanQRCodeSheet = false + @State var showQRCodeScanner = false @State private var connReqInvitation: String = "" @State private var contactConnection: PendingContactConnection? = nil @State private var creatingConnReq = false @@ -73,7 +73,7 @@ struct NewChatView: View { retryButton() } case .connect: - ConnectView(showScanQRCodeSheet: showScanQRCodeSheet) + ConnectView(showQRCodeScanner: showQRCodeScanner) } } .onChange(of: selection) { sel in @@ -193,7 +193,7 @@ private struct InviteView: View { .padding() .background( RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color(uiColor: .systemBackground)) + .fill(Color(uiColor: .secondarySystemGroupedBackground)) ) .padding(.horizontal) .listRowBackground(Color.clear) @@ -217,11 +217,10 @@ private enum ConnectAlert: Identifiable { private struct ConnectView: View { @Environment(\.dismiss) var dismiss: DismissAction - @State var showScanQRCodeSheet = false - @State private var connectionLink: String = "" + @State var showQRCodeScanner = false + @State private var pastedLink: String = "" @State private var alert: ConnectAlert? @State private var sheet: PlanAndConnectActionSheet? - @State private var scannedLink: String = "" var body: some View { viewBody() @@ -232,17 +231,6 @@ private struct ConnectView: View { } } .actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) } - .sheet(isPresented: $showScanQRCodeSheet) { - if #available(iOS 16.0, *) { - ScanConnectionCodeView(scannedLink: $scannedLink) - .presentationDetents([.fraction(0.8)]) - } else { - ScanConnectionCodeView(scannedLink: $scannedLink) - } - } - .onChange(of: scannedLink) { link in - connect(link) - } } @ViewBuilder private func viewBody() -> some View { @@ -250,19 +238,17 @@ private struct ConnectView: View { pasteLinkView() } - Section("Or scan QR code") { - scanQRCodeButton() - } + scanCodeView() } @ViewBuilder private func pasteLinkView() -> some View { - if connectionLink == "" { + if pastedLink == "" { Button { if let str = UIPasteboard.general.string { let link = str.trimmingCharacters(in: .whitespaces) if checkParsedLink(link) { - connectionLink = link - connect(connectionLink) + pastedLink = link + connect(pastedLink) } else { alert = .connectSomeAlert(alert: .someAlert( alert: mkAlert(title: "Invalid link", message: "The text you pasted is not a SimpleX link."), @@ -276,9 +262,9 @@ private struct ConnectView: View { .frame(maxWidth: .infinity, alignment: .center) } else { HStack { - linkTextView(connectionLink) + linkTextView(pastedLink) Button { - connectionLink = "" + pastedLink = "" } label: { Image(systemName: "xmark.circle") } @@ -296,6 +282,49 @@ private struct ConnectView: View { } } + private func scanCodeView() -> some View { + Section("Or scan QR code") { + if showQRCodeScanner { + CodeScannerView(codeTypes: [.qr], completion: processQRCode) + .aspectRatio(1, contentMode: .fit) + .cornerRadius(12) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .padding(.horizontal) + } else { + Button { + showQRCodeScanner = true + } label: { + Text("Tap to scan") + } + .frame(maxWidth: .infinity, alignment: .center) + } + } + } + + // TODO scan is only attempted once + private func processQRCode(_ resp: Result) { + switch resp { + case let .success(r): + let link = r.string + if checkParsedLink(link) { + connect(link) + } else { + alert = .connectSomeAlert(alert: .someAlert( + alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."), + id: "pasteLinkView checkParsedLink error" + )) + } + case let .failure(e): + logger.error("processQRCode QR code error: \(e.localizedDescription)") + alert = .connectSomeAlert(alert: .someAlert( + alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"), + id: "processQRCode failure" + )) + } + } + private func connect(_ link: String) { planAndConnect( link, @@ -305,53 +334,6 @@ private struct ConnectView: View { incognito: nil ) } - - private func scanQRCodeButton() -> some View { - Button { - showScanQRCodeSheet = true - } label: { - settingsRow("qrcode") { - Text("Scan code") - } - } - } -} - -private struct ScanConnectionCodeView: View { - @Environment(\.dismiss) var dismiss: DismissAction - @Binding var scannedLink: String - - var body: some View { - VStack(alignment: .leading) { - Text("Scan QR code") - .font(.largeTitle) - .bold() - .fixedSize(horizontal: false, vertical: true) - .padding(.vertical) - - CodeScannerView(codeTypes: [.qr], completion: processQRCode) - .aspectRatio(1, contentMode: .fit) - .cornerRadius(12) - .padding(.top) - - Text("If you cannot meet in person, you can **scan QR code in the video call**, or your contact can share an invitation link.") - .padding(.top) - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding() - } - - private func processQRCode(_ resp: Result) { - switch resp { - case let .success(r): - scannedLink = r.string - dismiss() - case let .failure(e): - logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)") - // TODO alert - dismiss() - } - } } struct InfoSheetButton: View { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 9b14004229..3c65af3388 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -179,7 +179,8 @@ 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; 64AEA4ED2B15D2A400334292 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEA4EC2B15D2A400334292 /* NewChatView.swift */; }; - 64AEA4EF2B15FEE100334292 /* NewChatSheetButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEA4EE2B15FEE100334292 /* NewChatSheetButton.swift */; }; + 64AEA4EF2B15FEE100334292 /* NewChatInviteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEA4EE2B15FEE100334292 /* NewChatInviteButton.swift */; }; + 64AEA4F12B18896400334292 /* NewChatScanButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEA4F02B18896400334292 /* NewChatScanButton.swift */; }; 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; }; 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; @@ -465,7 +466,8 @@ 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; 64AEA4EC2B15D2A400334292 /* NewChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = ""; }; - 64AEA4EE2B15FEE100334292 /* NewChatSheetButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatSheetButton.swift; sourceTree = ""; }; + 64AEA4EE2B15FEE100334292 /* NewChatInviteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatInviteButton.swift; sourceTree = ""; }; + 64AEA4F02B18896400334292 /* NewChatScanButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatScanButton.swift; sourceTree = ""; }; 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; }; 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; @@ -741,7 +743,8 @@ 5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */, 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */, 64AEA4EC2B15D2A400334292 /* NewChatView.swift */, - 64AEA4EE2B15FEE100334292 /* NewChatSheetButton.swift */, + 64AEA4EE2B15FEE100334292 /* NewChatInviteButton.swift */, + 64AEA4F02B18896400334292 /* NewChatScanButton.swift */, ); path = NewChat; sourceTree = ""; @@ -1121,7 +1124,7 @@ 5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */, 644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */, 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */, - 64AEA4EF2B15FEE100334292 /* NewChatSheetButton.swift in Sources */, + 64AEA4EF2B15FEE100334292 /* NewChatInviteButton.swift in Sources */, 5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */, 5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */, 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */, @@ -1247,6 +1250,7 @@ 184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */, 5CB634AD29E46CF70066AD6B /* LocalAuthView.swift in Sources */, 18415FEFE153C5920BFB7828 /* GroupWelcomeView.swift in Sources */, + 64AEA4F12B18896400334292 /* NewChatScanButton.swift in Sources */, 18415F9A2D551F9757DA4654 /* CIVideoView.swift in Sources */, 184158C131FDB829D8A117EA /* VideoPlayerView.swift in Sources */, );