From 5265667c0cdfeeb5dc114d6304ce469c112c9ef5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 24 Oct 2022 11:25:36 +0100 Subject: [PATCH] ios: settings to auto-accept contact requests (#1246) * ios: settings to auto-accept contact requests * use NavigationView * fix share sheet, layout * move buttons --- .../ChatList/ContactConnectionInfo.swift | 5 +- .../Shared/Views/NewChat/AddContactView.swift | 5 - .../Shared/Views/NewChat/CreateLinkView.swift | 23 ++- .../UserSettings/AcceptRequestsView.swift | 131 ++++++++++++++++++ .../Views/UserSettings/UserAddress.swift | 36 +++-- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/SimpleXChat/APITypes.swift | 5 + apps/ios/SimpleXChat/ChatTypes.swift | 2 +- 8 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index 99c07a6795..669e483ec8 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -62,8 +62,9 @@ struct ContactConnectionInfo: View { if contactConnection.initiated, let connReqInv = contactConnection.connReqInv { NavigationLink { - AddContactView(contactConnection: contactConnection, connReqInvitation: connReqInv, viaNavLink: true) - .navigationBarTitleDisplayMode(.inline) + AddContactView(contactConnection: contactConnection, connReqInvitation: connReqInv) + .navigationTitle(CreateLinkTab.oneTime.title) + .navigationBarTitleDisplayMode(.large) } label: { Label("Show QR code", systemImage: "qrcode") .foregroundColor(contactConnection.incognito ? .indigo : .accentColor) diff --git a/apps/ios/Shared/Views/NewChat/AddContactView.swift b/apps/ios/Shared/Views/NewChat/AddContactView.swift index 5fa9be654e..18c2e56bac 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactView.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactView.swift @@ -14,15 +14,10 @@ struct AddContactView: View { @EnvironmentObject private var chatModel: ChatModel var contactConnection: PendingContactConnection? = nil var connReqInvitation: String - var viaNavLink = false var body: some View { ScrollView { VStack(alignment: .leading) { - Text("One-time invitation link") - .font(.largeTitle) - .bold() - .padding(viaNavLink ? .bottom : .vertical) Text("Your contact can scan it from the app.") .padding(.bottom, 4) if (contactConnection?.incognito ?? chatModel.incognito) { diff --git a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift b/apps/ios/Shared/Views/NewChat/CreateLinkView.swift index 8e81cd02ec..4504361bd7 100644 --- a/apps/ios/Shared/Views/NewChat/CreateLinkView.swift +++ b/apps/ios/Shared/Views/NewChat/CreateLinkView.swift @@ -11,6 +11,13 @@ import SwiftUI enum CreateLinkTab { case oneTime case longTerm + + var title: LocalizedStringKey { + switch self { + case .oneTime: return "One-time invitation link" + case .longTerm: return "Your contact address" + } + } } struct CreateLinkView: View { @@ -21,8 +28,18 @@ struct CreateLinkView: View { var viaNavLink = false var body: some View { + if viaNavLink { + createLinkView() + } else { + NavigationView { + createLinkView() + } + } + } + + private func createLinkView() -> some View { TabView(selection: $selection) { - AddContactView(connReqInvitation: connReqInvitation, viaNavLink: viaNavLink) + AddContactView(connReqInvitation: connReqInvitation) .tabItem { Label( connReqInvitation == "" @@ -32,7 +49,7 @@ struct CreateLinkView: View { ) } .tag(CreateLinkTab.oneTime) - UserAddress(viaNavLink: viaNavLink) + UserAddress() .tabItem { Label("Your contact address", systemImage: "infinity.circle") } @@ -45,6 +62,8 @@ struct CreateLinkView: View { } .onAppear { m.connReqInv = connReqInvitation } .onDisappear { m.connReqInv = nil } + .navigationTitle(selection.title) + .navigationBarTitleDisplayMode(.large) } private func createInvitation() { diff --git a/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift b/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift new file mode 100644 index 0000000000..0e61658c93 --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/AcceptRequestsView.swift @@ -0,0 +1,131 @@ +// +// AcceptRequestsView.swift +// SimpleX (iOS) +// +// Created by Evgeny on 23/10/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct AcceptRequestsView: View { + @EnvironmentObject private var m: ChatModel + @State var contactLink: UserContactLink + @State private var a = AutoAcceptState() + @State private var saved = AutoAcceptState() + @FocusState private var keyboardVisible: Bool + + var body: some View { + List { + Section { + settingsRow("checkmark") { + Toggle("Automatically", isOn: $a.enable) + } + if a.enable { + settingsRow( + a.incognito ? "theatermasks.fill" : "theatermasks", + color: a.incognito ? .indigo : .secondary + ) { + Toggle("Incognito", isOn: $a.incognito) + } + } + } header: { + Text("Accept requests") + } footer: { + saveButtons() + } + if a.enable { + Section { + TextEditor(text: $a.welcomeText) + .focused($keyboardVisible) + .padding(.horizontal, -5) + .padding(.top, -8) + .frame(height: 90, alignment: .topLeading) + .frame(maxWidth: .infinity, alignment: .leading) + } header: { + Text("Welcome message") + } + } + } + .onAppear { + a = AutoAcceptState(contactLink: contactLink) + saved = a + } + .onChange(of: a.enable) { _ in + if !a.enable { a = AutoAcceptState() } + } + } + + @ViewBuilder private func saveButtons() -> some View { + HStack { + Button { + a = saved + } label: { + Label("Cancel", systemImage: "arrow.counterclockwise") + } + Spacer() + Button { + Task { + do { + if let link = try await userAddressAutoAccept(a.autoAccept) { + contactLink = link + m.userAddress = link + saved = a + } + } catch let error { + logger.error("userAddressAutoAccept error: \(responseError(error))") + } + } + } label: { + Label("Save", systemImage: "checkmark") + } + } + .font(.body) + .disabled(a == saved) + } + + private struct AutoAcceptState: Equatable { + var enable = false + var incognito = false + var welcomeText = "" + + init(enable: Bool = false, incognito: Bool = false, welcomeText: String = "") { + self.enable = enable + self.incognito = incognito + self.welcomeText = welcomeText + } + + init(contactLink: UserContactLink) { + if let aa = contactLink.autoAccept { + enable = true + incognito = aa.acceptIncognito + if let msg = aa.autoReply { + welcomeText = msg.text + } else { + welcomeText = "" + } + } else { + enable = false + incognito = false + welcomeText = "" + } + } + + var autoAccept: AutoAccept? { + if enable { + var autoReply: MsgContent? = nil + let s = welcomeText.trimmingCharacters(in: .whitespacesAndNewlines) + if s != "" { autoReply = .text(s) } + return AutoAccept(acceptIncognito: incognito, autoReply: autoReply) + } + return nil + } + } +} + +struct AcceptRequestsView_Previews: PreviewProvider { + static var previews: some View { + AcceptRequestsView(contactLink: UserContactLink(connReqContact: "")) + } +} diff --git a/apps/ios/Shared/Views/UserSettings/UserAddress.swift b/apps/ios/Shared/Views/UserSettings/UserAddress.swift index 06bc2ab7b3..89c628c115 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddress.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddress.swift @@ -12,7 +12,7 @@ import SimpleXChat struct UserAddress: View { @EnvironmentObject private var chatModel: ChatModel @State private var alert: UserAddressAlert? - var viaNavLink = false + @State private var showAcceptRequests = false private enum UserAddressAlert: Identifiable { case deleteAddress @@ -29,28 +29,39 @@ struct UserAddress: View { var body: some View { ScrollView { VStack (alignment: .leading) { - Text("Your contact address") - .font(.largeTitle) - .bold() - .padding(viaNavLink ? .bottom : .vertical) Text("You can share your address as a link or as a QR code - anybody will be able to connect to you. You won't lose your contacts if you later delete it.") .padding(.bottom) if let userAdress = chatModel.userAddress { QRCode(uri: userAdress.connReqContact) HStack { Button { - showShareSheet(items: [userAdress]) + showShareSheet(items: [userAdress.connReqContact]) } label: { - Label("Share link", systemImage: "square.and.arrow.up") + HStack { + Image(systemName: "square.and.arrow.up") + Text("Share link") + } } .padding() - - Button(role: .destructive) { alert = .deleteAddress } label: { - Label("Delete address", systemImage: "trash") + NavigationLink { + if let contactLink = chatModel.userAddress { + AcceptRequestsView(contactLink: contactLink) + .navigationTitle("Contact requests") + .navigationBarTitleDisplayMode(.large) + } + } label: { + HStack { + Text("Contact requests") + Image(systemName: "chevron.right") + } } .padding() } .frame(maxWidth: .infinity) + Button(role: .destructive) { alert = .deleteAddress } label: { + Label("Delete address", systemImage: "trash") + } + .frame(maxWidth: .infinity) } else { Button { Task { @@ -71,6 +82,11 @@ struct UserAddress: View { } .padding() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .sheet(isPresented: $showAcceptRequests) { + if let contactLink = chatModel.userAddress { + AcceptRequestsView(contactLink: contactLink) + } + } .alert(item: $alert) { alert in switch alert { case .deleteAddress: diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index b966c0e9dc..6c1d6ed9d3 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -94,6 +94,7 @@ 5CCA7DEF2901E32900C8FEBA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */; }; 5CCA7DF02901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */; }; 5CCA7DF12901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */; }; + 5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */; }; 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; }; 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; }; 5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; }; @@ -294,6 +295,7 @@ 5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a"; sourceTree = ""; }; 5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a"; sourceTree = ""; }; + 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptRequestsView.swift; sourceTree = ""; }; 5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = ""; }; 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = ""; }; 5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -573,6 +575,7 @@ 5C05DF522840AA1D00C683F9 /* CallSettings.swift */, 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */, 5CB924E327A8683A00ACCCDD /* UserAddress.swift */, + 5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */, 5CB924E027A867BA00ACCCDD /* UserProfile.swift */, 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */, 640F50E227CF991C001E05C2 /* SMPServers.swift */, @@ -892,6 +895,7 @@ 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */, 5C029EAA283942EA004A9677 /* CallController.swift in Sources */, + 5CCA7DF32905735700C8FEBA /* AcceptRequestsView.swift in Sources */, 5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */, 5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */, 648010AB281ADD15009009B9 /* CIFileView.swift in Sources */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 7dbf1aff8e..a5388f197d 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -726,6 +726,11 @@ public struct AutoAccept: Codable { public var acceptIncognito: Bool public var autoReply: MsgContent? + public init(acceptIncognito: Bool, autoReply: MsgContent? = nil) { + self.acceptIncognito = acceptIncognito + self.autoReply = autoReply + } + static func cmdString(_ autoAccept: AutoAccept?) -> String { guard let autoAccept = autoAccept else { return "off" } let s = "on" + (autoAccept.acceptIncognito ? " incognito=on" : "") diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 1feab30c7e..f7b76b634f 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1199,7 +1199,7 @@ public enum MsgContent { // TODO include original JSON, possibly using https://github.com/zoul/generic-json-swift case unknown(type: String, text: String) - var text: String { + public var text: String { get { switch self { case let .text(text): return text