From d8d47d706d50fa3f18d1b5ac8a736deb63b05b10 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:56:12 +0400 Subject: [PATCH] ios: connection plan improvements; remove browser mode for simplex links (#3237) --- apps/ios/Shared/ContentView.swift | 5 +- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 4 +- .../Views/Chat/ChatItem/MsgContentView.swift | 6 +- .../Views/Chat/Group/GroupLinkView.swift | 4 +- .../Chat/Group/GroupMemberInfoView.swift | 4 +- .../ChatList/ContactConnectionInfo.swift | 2 +- .../Shared/Views/NewChat/AddContactView.swift | 4 +- .../Shared/Views/NewChat/NewChatButton.swift | 56 ++++++++++++++++--- apps/ios/Shared/Views/NewChat/QRCode.swift | 16 ++++++ .../Onboarding/CreateSimpleXAddress.swift | 4 +- .../Views/UserSettings/PrivacySettings.swift | 8 +-- .../Views/UserSettings/SettingsView.swift | 2 +- .../Views/UserSettings/UserAddressView.swift | 4 +- apps/ios/SimpleXChat/APITypes.swift | 6 +- apps/ios/SimpleXChat/ChatTypes.swift | 2 +- 15 files changed, 88 insertions(+), 39 deletions(-) diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 04eadac2c..b69ccbb7c 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -292,10 +292,7 @@ struct ContentView: View { var path = url.path if (path == "/contact" || path == "/invitation") { path.removeFirst() - // TODO normalize in backend; revert - // let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") - var link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") - link = link.starts(with: "simplex:/") ? link.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/") : link + let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") planAndConnect( link, showAlert: showPlanAndConnectAlert, diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 81412bf31..5438eb13b 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -168,9 +168,9 @@ struct ChatInfoView: View { if let contactLink = contact.contactLink { Section { - QRCode(uri: contactLink) + SimpleXLinkQRCode(uri: contactLink) Button { - showShareSheet(items: [contactLink]) + showShareSheet(items: [simplexChatLink(contactLink)]) } label: { Label("Share address", systemImage: "square.and.arrow.up") } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 498b3cb2e..8b757ed1a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -121,13 +121,11 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text { case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary) case let .colored(color): return Text(t).foregroundColor(color.uiColor) case .uri: return linkText(t, t, preview, prefix: "") - case let .simplexLink(linkType, simplexUri, trustedUri, smpHosts): + case let .simplexLink(linkType, simplexUri, smpHosts): switch privacySimplexLinkModeDefault.get() { case .description: return linkText(simplexLinkText(linkType, smpHosts), simplexUri, preview, prefix: "") case .full: return linkText(t, simplexUri, preview, prefix: "") - case .browser: return trustedUri - ? linkText(t, t, preview, prefix: "") - : linkText(t, t, preview, prefix: "", color: .red, uiColor: .red) + case .browser: return linkText(t, simplexUri, preview, prefix: "") } case .email: return linkText(t, t, preview, prefix: "mailto:") case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:") diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index 3731e0c4d..781870bf5 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -41,9 +41,9 @@ struct GroupLinkView: View { } } .frame(height: 36) - QRCode(uri: groupLink) + SimpleXLinkQRCode(uri: groupLink) Button { - showShareSheet(items: [groupLink]) + showShareSheet(items: [simplexChatLink(groupLink)]) } label: { Label("Share link", systemImage: "square.and.arrow.up") } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 3d10101de..4b6445814 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -94,9 +94,9 @@ struct GroupMemberInfoView: View { if let contactLink = member.contactLink { Section { - QRCode(uri: contactLink) + SimpleXLinkQRCode(uri: contactLink) Button { - showShareSheet(items: [contactLink]) + showShareSheet(items: [simplexChatLink(contactLink)]) } label: { Label("Share address", systemImage: "square.and.arrow.up") } diff --git a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift index 3e42d2f20..7c973c73c 100644 --- a/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift +++ b/apps/ios/Shared/Views/ChatList/ContactConnectionInfo.swift @@ -61,7 +61,7 @@ struct ContactConnectionInfo: View { if contactConnection.initiated, let connReqInv = contactConnection.connReqInv { - QRCode(uri: connReqInv) + SimpleXLinkQRCode(uri: simplexChatLink(connReqInv)) incognitoEnabled() shareLinkButton(connReqInv) oneTimeLinkLearnMoreButton() diff --git a/apps/ios/Shared/Views/NewChat/AddContactView.swift b/apps/ios/Shared/Views/NewChat/AddContactView.swift index 31b6b64f3..344a8d1f9 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactView.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactView.swift @@ -21,7 +21,7 @@ struct AddContactView: View { List { Section { if connReqInvitation != "" { - QRCode(uri: connReqInvitation) + SimpleXLinkQRCode(uri: connReqInvitation) } else { ProgressView() .progressViewStyle(.circular) @@ -99,7 +99,7 @@ func sharedProfileInfo(_ incognito: Bool) -> Text { func shareLinkButton(_ connReqInvitation: String) -> some View { Button { - showShareSheet(items: [connReqInvitation]) + showShareSheet(items: [simplexChatLink(connReqInvitation)]) } label: { settingsRow("square.and.arrow.up") { Text("Share 1-time link") diff --git a/apps/ios/Shared/Views/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/NewChat/NewChatButton.swift index e4fbd8bd4..8d095e907 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatButton.swift @@ -62,7 +62,9 @@ enum PlanAndConnectAlert: Identifiable { case ownInvitationLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) case invitationLinkConnecting(connectionLink: String) case ownContactAddressConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) + case contactAddressConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) case groupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) + case groupLinkConnectingConfirmReconnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool) case groupLinkConnecting(connectionLink: String, groupInfo: GroupInfo?) var id: String { @@ -70,7 +72,9 @@ enum PlanAndConnectAlert: Identifiable { case let .ownInvitationLinkConfirmConnect(connectionLink, _, _): return "ownInvitationLinkConfirmConnect \(connectionLink)" case let .invitationLinkConnecting(connectionLink): return "invitationLinkConnecting \(connectionLink)" case let .ownContactAddressConfirmConnect(connectionLink, _, _): return "ownContactAddressConfirmConnect \(connectionLink)" + case let .contactAddressConnectingConfirmReconnect(connectionLink, _, _): return "contactAddressConnectingConfirmReconnect \(connectionLink)" case let .groupLinkConfirmConnect(connectionLink, _, _): return "groupLinkConfirmConnect \(connectionLink)" + case let .groupLinkConnectingConfirmReconnect(connectionLink, _, _): return "groupLinkConnectingConfirmReconnect \(connectionLink)" case let .groupLinkConnecting(connectionLink, _): return "groupLinkConnecting \(connectionLink)" } } @@ -103,6 +107,16 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert { ), secondaryButton: .cancel() ) + case let .contactAddressConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): + return Alert( + title: Text("Repeat connection request?"), + message: Text("You have already requested connection via this address!"), + primaryButton: .destructive( + Text(incognito ? "Connect incognito" : "Connect"), + action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) } + ), + secondaryButton: .cancel() + ) case let .groupLinkConfirmConnect(connectionLink, connectionPlan, incognito): return Alert( title: Text("Join group?"), @@ -113,6 +127,16 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert { ), secondaryButton: .cancel() ) + case let .groupLinkConnectingConfirmReconnect(connectionLink, connectionPlan, incognito): + return Alert( + title: Text("Repeat join request?"), + message: Text("You are already joining the group via this link!"), + primaryButton: .destructive( + Text(incognito ? "Join incognito" : "Join"), + action: { connectViaLink(connectionLink, connectionPlan: connectionPlan, dismiss: dismiss, incognito: incognito) } + ), + secondaryButton: .cancel() + ) case let .groupLinkConnecting(_, groupInfo): if let groupInfo = groupInfo { return Alert( @@ -130,13 +154,13 @@ func planAndConnectAlert(_ alert: PlanAndConnectAlert, dismiss: Bool) -> Alert { enum PlanAndConnectActionSheet: Identifiable { case askCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan?, title: LocalizedStringKey) - case ownLinkAskCurrentOrIncognitoProfile(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey) + case askCurrentOrIncognitoProfileDestructive(connectionLink: String, connectionPlan: ConnectionPlan, title: LocalizedStringKey) case ownGroupLinkConfirmConnect(connectionLink: String, connectionPlan: ConnectionPlan, incognito: Bool?, groupInfo: GroupInfo) var id: String { switch self { case let .askCurrentOrIncognitoProfile(connectionLink, _, _): return "askCurrentOrIncognitoProfile \(connectionLink)" - case let .ownLinkAskCurrentOrIncognitoProfile(connectionLink, _, _): return "ownLinkAskCurrentOrIncognitoProfile \(connectionLink)" + case let .askCurrentOrIncognitoProfileDestructive(connectionLink, _, _): return "askCurrentOrIncognitoProfileDestructive \(connectionLink)" case let .ownGroupLinkConfirmConnect(connectionLink, _, _, _): return "ownGroupLinkConfirmConnect \(connectionLink)" } } @@ -153,7 +177,7 @@ func planAndConnectActionSheet(_ sheet: PlanAndConnectActionSheet, dismiss: Bool .cancel() ] ) - case let .ownLinkAskCurrentOrIncognitoProfile(connectionLink, connectionPlan, title): + case let .askCurrentOrIncognitoProfileDestructive(connectionLink, connectionPlan, title): return ActionSheet( title: Text(title), buttons: [ @@ -211,7 +235,7 @@ func planAndConnect( if let incognito = incognito { showAlert(.ownInvitationLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) } else { - showActionSheet(.ownLinkAskCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own one-time link!")) } case let .connecting(contact_): logger.debug("planAndConnect, .invitationLink, .connecting, incognito=\(incognito?.description ?? "nil")") @@ -238,10 +262,17 @@ func planAndConnect( if let incognito = incognito { showAlert(.ownContactAddressConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) } else { - showActionSheet(.ownLinkAskCurrentOrIncognitoProfile(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "Connect to yourself?\nThis is your own SimpleX address!")) } - case let .connecting(contact): - logger.debug("planAndConnect, .contactAddress, .connecting, incognito=\(incognito?.description ?? "nil")") + case .connectingConfirmReconnect: + logger.debug("planAndConnect, .contactAddress, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + showAlert(.contactAddressConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You have already requested connection!\nRepeat connection request?")) + } + case let .connectingProhibit(contact): + logger.debug("planAndConnect, .contactAddress, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") openKnownContact(contact, dismiss: dismiss) { AlertManager.shared.showAlert(contactAlreadyConnectingAlert(contact)) } case let .known(contact): logger.debug("planAndConnect, .contactAddress, .known, incognito=\(incognito?.description ?? "nil")") @@ -258,8 +289,15 @@ func planAndConnect( case let .ownLink(groupInfo): logger.debug("planAndConnect, .groupLink, .ownLink, incognito=\(incognito?.description ?? "nil")") showActionSheet(.ownGroupLinkConfirmConnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito, groupInfo: groupInfo)) - case let .connecting(groupInfo_): - logger.debug("planAndConnect, .groupLink, .connecting, incognito=\(incognito?.description ?? "nil")") + case .connectingConfirmReconnect: + logger.debug("planAndConnect, .groupLink, .connectingConfirmReconnect, incognito=\(incognito?.description ?? "nil")") + if let incognito = incognito { + showAlert(.groupLinkConnectingConfirmReconnect(connectionLink: connectionLink, connectionPlan: connectionPlan, incognito: incognito)) + } else { + showActionSheet(.askCurrentOrIncognitoProfileDestructive(connectionLink: connectionLink, connectionPlan: connectionPlan, title: "You are already joining the group!\nRepeat join request?")) + } + case let .connectingProhibit(groupInfo_): + logger.debug("planAndConnect, .groupLink, .connectingProhibit, incognito=\(incognito?.description ?? "nil")") showAlert(.groupLinkConnecting(connectionLink: connectionLink, groupInfo: groupInfo_)) case let .known(groupInfo): logger.debug("planAndConnect, .groupLink, .known, incognito=\(incognito?.description ?? "nil")") diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index 0785826fa..4bec3f8e6 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -28,6 +28,22 @@ struct MutableQRCode: View { } } +struct SimpleXLinkQRCode: View { + let uri: String + var withLogo: Bool = true + var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) + + var body: some View { + QRCode(uri: simplexChatLink(uri), withLogo: withLogo, tintColor: tintColor) + } +} + +func simplexChatLink(_ uri: String) -> String { + uri.starts(with: "simplex:/") + ? uri.replacingOccurrences(of: "simplex:/", with: "https://simplex.chat/") + : uri +} + struct QRCode: View { let uri: String var withLogo: Bool = true diff --git a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift index 049c4bee0..249180686 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateSimpleXAddress.swift @@ -31,7 +31,7 @@ struct CreateSimpleXAddress: View { Spacer() if let userAddress = m.userAddress { - QRCode(uri: userAddress.connReqContact) + SimpleXLinkQRCode(uri: userAddress.connReqContact) .frame(maxHeight: g.size.width) shareQRCodeButton(userAddress) .frame(maxWidth: .infinity) @@ -126,7 +126,7 @@ struct CreateSimpleXAddress: View { private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View { Button { - showShareSheet(items: [userAddress.connReqContact]) + showShareSheet(items: [simplexChatLink(userAddress.connReqContact)]) } label: { Label("Share", systemImage: "square.and.arrow.up") } diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 16555fad3..71ff7b88b 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -93,7 +93,9 @@ struct PrivacySettings: View { } settingsRow("link") { Picker("SimpleX links", selection: $simplexLinkMode) { - ForEach(SimpleXLinkMode.values) { mode in + ForEach( + SimpleXLinkMode.values + (SimpleXLinkMode.values.contains(simplexLinkMode) ? [] : [simplexLinkMode]) + ) { mode in Text(mode.text) } } @@ -104,10 +106,6 @@ struct PrivacySettings: View { } } header: { Text("Chats") - } footer: { - if case .browser = simplexLinkMode { - Text("Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.") - } } Section { diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index f076a4eb7..1cc859f49 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -93,7 +93,7 @@ enum SimpleXLinkMode: String, Identifiable { case full case browser - static var values: [SimpleXLinkMode] = [.description, .full, .browser] + static var values: [SimpleXLinkMode] = [.description, .full] public var id: Self { self } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 86bf0048b..60e5cc7b0 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -190,7 +190,7 @@ struct UserAddressView: View { @ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View { Section { - MutableQRCode(uri: Binding.constant(userAddress.connReqContact)) + MutableQRCode(uri: Binding.constant(simplexChatLink(userAddress.connReqContact))) shareQRCodeButton(userAddress) if MFMailComposeViewController.canSendMail() { shareViaEmailButton(userAddress) @@ -248,7 +248,7 @@ struct UserAddressView: View { private func shareQRCodeButton(_ userAddress: UserContactLink) -> some View { Button { - showShareSheet(items: [userAddress.connReqContact]) + showShareSheet(items: [simplexChatLink(userAddress.connReqContact)]) } label: { settingsRow("square.and.arrow.up") { Text("Share address") diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index ddab4c73b..53da91b04 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -886,14 +886,16 @@ public enum InvitationLinkPlan: Decodable { public enum ContactAddressPlan: Decodable { case ok case ownLink - case connecting(contact: Contact) + case connectingConfirmReconnect + case connectingProhibit(contact: Contact) case known(contact: Contact) } public enum GroupLinkPlan: Decodable { case ok case ownLink(groupInfo: GroupInfo) - case connecting(groupInfo_: GroupInfo?) + case connectingConfirmReconnect + case connectingProhibit(groupInfo_: GroupInfo?) case known(groupInfo: GroupInfo) } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index caaccb545..c31786c70 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -3068,7 +3068,7 @@ public enum Format: Decodable, Equatable { case secret case colored(color: FormatColor) case uri - case simplexLink(linkType: SimplexLinkType, simplexUri: String, trustedUri: Bool, smpHosts: [String]) + case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String]) case email case phone }