Paste link to connect (#551)

* initial implementation of textbox

* paste to connect box implemented (and tested) in android

* first pass at pastebox in iOS

* clean up iOS implementation

* put paste link page in for groups in android

* initial inclusion in iOS UI

* refactor naming

* lint kotlin

* fix typo

* ios: update "connect via link" ui, refactor connecting via link to use the one function

* android: update paste link UI

* add russian translations

* update translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update translations

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
IanRDavies
2022-04-25 07:54:07 +01:00
committed by GitHub
co-authored by JRoberts Evgeny Poberezkin
parent 48ba6472b6
commit e87660974e
22 changed files with 561 additions and 247 deletions
+8 -8
View File
@@ -496,42 +496,42 @@ func apiAddContact() throws -> String {
throw r
}
func apiConnect(connReq: String) async throws -> Bool {
func apiConnect(connReq: String) async throws -> ConnReqType? {
let r = await chatSendCmd(.connect(connReq: connReq))
let am = AlertManager.shared
switch r {
case .sentConfirmation: return true
case .sentInvitation: return true
case .sentConfirmation: return .invitation
case .sentInvitation: return .contact
case let .contactAlreadyExists(contact):
am.showAlertMsg(
title: "Contact already exists",
message: "You are already connected to \(contact.displayName) via this link."
)
return false
return nil
case .chatCmdError(.error(.invalidConnReq)):
am.showAlertMsg(
title: "Invalid connection link",
message: "Please check that you used the correct link or ask your contact to send you another one."
)
return false
return nil
case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))):
am.showAlertMsg(
title: "Connection timeout",
message: "Please check your network connection and try again."
)
return false
return nil
case .chatCmdError(.errorAgent(.BROKER(.NETWORK))):
am.showAlertMsg(
title: "Connection error",
message: "Please check your network connection and try again."
)
return false
return nil
case .chatCmdError(.errorAgent(.SMP(.AUTH))):
am.showAlertMsg(
title: "Connection error (AUTH)",
message: "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection."
)
return false
return nil
default: throw r
}
}
@@ -78,23 +78,12 @@ struct ChatListView: View {
let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)")
let title: LocalizedStringKey
if case .contact = action { title = "Connect via contact link?" }
else { title = "Connect via invitation link?" }
else { title = "Connect via one-time link?" }
return Alert(
title: Text(title),
message: Text("Your profile will be sent to the contact that you received this link from"),
primaryButton: .default(Text("Connect")) {
DispatchQueue.main.async {
Task {
do {
let ok = try await apiConnect(connReq: link)
if ok { connectionReqSentAlert(action) }
} catch {
let err = error.localizedDescription
AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(err)")
logger.debug("ChatListView.connectViaUrlAlert: apiConnect error: \(err)")
}
}
}
connectViaLink(link)
},
secondaryButton: .cancel()
)
@@ -11,7 +11,6 @@ import CoreImage.CIFilterBuiltins
struct AddContactView: View {
var connReqInvitation: String
var body: some View {
VStack {
Text("Add contact")
@@ -12,26 +12,27 @@ struct NewChatButton: View {
@State private var showAddChat = false
@State private var addContact = false
@State private var connReqInvitation: String = ""
@State private var connectContact = false
@State private var createGroup = false
@State private var scanToConnect = false
@State private var pasteToConnect = false
var body: some View {
Button { showAddChat = true } label: {
Image(systemName: "person.crop.circle.badge.plus")
}
.confirmationDialog("Start new chat", isPresented: $showAddChat, titleVisibility: .visible) {
Button("Add contact") { addContactAction() }
Button("Scan QR code") { connectContact = true }
Button("Create group") { createGroup = true }
.disabled(true)
.confirmationDialog("Add contact to start a new chat", isPresented: $showAddChat, titleVisibility: .visible) {
Button("Create link / QR code") { addContactAction() }
Button("Paste received link") { pasteToConnect = true }
Button("Scan QR code") { scanToConnect = true }
}
.sheet(isPresented: $addContact, content: {
AddContactView(connReqInvitation: connReqInvitation)
})
.sheet(isPresented: $connectContact, content: {
connectContactSheet()
.sheet(isPresented: $scanToConnect, content: {
ScanToConnectView(openedSheet: $scanToConnect)
})
.sheet(isPresented: $pasteToConnect, content: {
PasteToConnectView(openedSheet: $pasteToConnect)
})
.sheet(isPresented: $createGroup, content: { CreateGroupView() })
}
func addContactAction() {
@@ -45,28 +46,6 @@ struct NewChatButton: View {
logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)")
}
}
func addContactSheet() -> some View {
AddContactView(connReqInvitation: connReqInvitation)
}
func connectContactSheet() -> some View {
ConnectContactView(completed: { err in
connectContact = false
DispatchQueue.global().async {
switch (err) {
case let .success(ok):
if ok { connectionReqSentAlert(.invitation) }
case let .failure(error):
connectionErrorAlert(error)
}
}
})
}
func connectionErrorAlert(_ error: Error) {
AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(error.localizedDescription)")
}
}
enum ConnReqType: Equatable {
@@ -74,6 +53,30 @@ enum ConnReqType: Equatable {
case invitation
}
func connectViaLink(_ connectionLink: String, _ openedSheet: Binding<Bool>? = nil) {
Task {
do {
let res = try await apiConnect(connReq: connectionLink)
DispatchQueue.main.async {
openedSheet?.wrappedValue = false
if let connReqType = res {
connectionReqSentAlert(connReqType)
}
}
} catch {
logger.error("connectViaLink apiConnect error: \(responseError(error))")
DispatchQueue.main.async {
openedSheet?.wrappedValue = false
connectionErrorAlert(error)
}
}
}
}
func connectionErrorAlert(_ error: Error) {
AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(error.localizedDescription)")
}
func connectionReqSentAlert(_ type: ConnReqType) {
AlertManager.shared.showAlertMsg(
title: "Connection request sent!",
@@ -0,0 +1,78 @@
//
// PasteToConnectView.swift
// SimpleX (iOS)
//
// Created by Ian Davies on 22/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
struct PasteToConnectView: View {
@Binding var openedSheet: Bool
@State private var connectionLink: String = ""
var body: some View {
VStack(alignment: .leading) {
Text("Connect via link")
.font(.title)
.padding([.bottom])
.frame(maxWidth: .infinity, alignment: .center)
Text("Paste the link you received into the box below to connect with your contact.")
.multilineTextAlignment(.leading)
Text("Your profile will be sent to the contact that you received this link from")
.multilineTextAlignment(.leading)
.padding(.bottom)
TextEditor(text: $connectionLink)
.onSubmit(connect)
.font(.body)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.allowsTightening(false)
.frame(height: 180)
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)
)
HStack(spacing: 20) {
if connectionLink == "" {
Button {
connectionLink = UIPasteboard.general.string ?? ""
} label: {
Label("Paste", systemImage: "doc.on.clipboard")
}
} else {
Button {
connectionLink = ""
} label: {
Label("Clear", systemImage: "multiply")
}
}
Spacer()
Button(action: connect, label: {
Label("Connect", systemImage: "link")
})
.disabled(connectionLink == "" || connectionLink.trimmingCharacters(in: .whitespaces).firstIndex(of: " ") != nil)
}
.frame(height: 48)
.padding(.bottom)
Text("You can also connect by clicking the link. If it opens in the browser, click **Open in mobile app** button")
.multilineTextAlignment(.leading)
}
.padding()
}
private func connect() {
connectViaLink(connectionLink.trimmingCharacters(in: .whitespaces), $openedSheet)
}
}
struct PasteToConnectView_Previews: PreviewProvider {
static var previews: some View {
@State var openedSheet: Bool = true
return PasteToConnectView(openedSheet: $openedSheet)
}
}
@@ -9,8 +9,8 @@
import SwiftUI
import CodeScanner
struct ConnectContactView: View {
var completed: ((Result<Bool, Error>) -> Void)
struct ScanToConnectView: View {
@Binding var openedSheet: Bool
var body: some View {
VStack {
@@ -37,24 +37,17 @@ struct ConnectContactView: View {
func processQRCode(_ resp: Result<ScanResult, ScanError>) {
switch resp {
case let .success(r):
Task {
do {
let ok = try await apiConnect(connReq: r.string)
completed(.success(ok))
} catch {
logger.error("ConnectContactView.processQRCode apiConnect error: \(error.localizedDescription)")
completed(.failure(error))
}
}
Task { connectViaLink(r.string, $openedSheet) }
case let .failure(e):
logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)")
completed(.failure(e))
openedSheet = false
}
}
}
struct ConnectContactView_Previews: PreviewProvider {
static var previews: some View {
return ConnectContactView(completed: {_ in })
@State var openedSheet: Bool = true
return ScanToConnectView(openedSheet: $openedSheet)
}
}