ios: settings to auto-accept contact requests (#1246)

* ios: settings to auto-accept contact requests

* use NavigationView

* fix share sheet, layout

* move buttons
This commit is contained in:
Evgeny Poberezkin
2022-10-24 11:25:36 +01:00
committed by GitHub
parent 15c1f9f9c8
commit 5265667c0c
8 changed files with 191 additions and 20 deletions

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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: ""))
}
}

View File

@@ -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:

View File

@@ -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 = "<group>"; };
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 = "<group>"; };
5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a"; sourceTree = "<group>"; };
5CCA7DF22905735700C8FEBA /* AcceptRequestsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptRequestsView.swift; sourceTree = "<group>"; };
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
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 */,

View File

@@ -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" : "")

View File

@@ -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