diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 1d4ef420ed..8293af6a65 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -17,6 +17,8 @@ final class ChatModel: ObservableObject { @Published var chatItems: [ChatItem] = [] @Published var terminalItems: [TerminalItem] = [] @Published var userAddress: String? + @Published var appOpenUrl: URL? + @Published var connectViaUrl = false } class User: Decodable { diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 94aaac5506..b898e3de46 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -402,10 +402,15 @@ private func encodeCJSON(_ value: T) -> [CChar] { } enum ChatError: Decodable { + case error(errorType: ChatErrorType) case errorStore(storeError: StoreError) // TODO other error cases } +enum ChatErrorType: Decodable { + case invalidConnReq +} + enum StoreError: Decodable { case userContactLinkNotFound // TODO other error cases diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index 0666668196..222ed168da 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -19,6 +19,11 @@ struct SimpleXApp: App { WindowGroup { ContentView() .environmentObject(chatModel) + .onOpenURL { url in + chatModel.appOpenUrl = url + chatModel.connectViaUrl = true + print(url) + } .onAppear() { chatModel.currentUser = chatGetUser() } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index caeb4468cf..33d66f54a3 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -11,11 +11,13 @@ import SwiftUI struct ChatListView: View { @EnvironmentObject var chatModel: ChatModel @State private var chatId: String? + @State private var connectAlert = false + @State private var connectError: Error? var user: User var body: some View { - return VStack { + VStack { // if chatModel.chats.isEmpty { // VStack { // Text("Hello chat") @@ -45,10 +47,49 @@ struct ChatListView: View { .listStyle(.plain) .toolbar { ChatListToolbar(width: geometry.size.width) } .navigationBarTitleDisplayMode(.inline) + .alert(isPresented: $connectAlert) { connectionErrorAlert() } } } + .alert(isPresented: $chatModel.connectViaUrl) { connectViaUrlAlert() } } } + + private func connectViaUrlAlert() -> Alert { + if let url = chatModel.appOpenUrl { + var path = url.path + if (path == "/contact" || path == "/invitation") { + path.removeFirst() + let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") + return Alert( + title: Text("Connect via \(path) link?"), + message: Text("Your profile will be sent to the contact that you received this link from: \(link)"), + primaryButton: .default(Text("Connect")) { + do { + try apiConnect(connReq: link) + } catch { + connectAlert = true + connectError = error + print(error) + } + chatModel.appOpenUrl = nil + }, secondaryButton: .cancel() { + chatModel.appOpenUrl = nil + } + ) + } else { + return Alert(title: Text("Error: URL not available")) + } + } else { + return Alert(title: Text("Error: URL not available")) + } + } + + private func connectionErrorAlert() -> Alert { + Alert( + title: Text("Connection error"), + message: Text(connectError?.localizedDescription ?? "") + ) + } } struct ChatListView_Previews: PreviewProvider { diff --git a/apps/ios/SimpleX (iOS).entitlements b/apps/ios/SimpleX (iOS).entitlements new file mode 100644 index 0000000000..3a6f1244b0 --- /dev/null +++ b/apps/ios/SimpleX (iOS).entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.developer.associated-domains + + applinks:simplex.chat + applinks:www.simplex.chat + applinks:simplex.chat?mode=developer + + + diff --git a/apps/ios/SimpleX--iOS--Info.plist b/apps/ios/SimpleX--iOS--Info.plist new file mode 100644 index 0000000000..08407cc1cf --- /dev/null +++ b/apps/ios/SimpleX--iOS--Info.plist @@ -0,0 +1,19 @@ + + + + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + chat.simplex.app + CFBundleURLSchemes + + simplex + + + + + diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 1cb858b560..c604bf60aa 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -108,6 +108,7 @@ 5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; 5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; 5C2E261127A30FEA00F70299 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = ""; }; + 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = ""; }; 5C44B69E27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w.a"; sourceTree = ""; }; 5C44B69F27A5FF22001C3154 /* libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.0.2-5okwuQXOXC78H7u8DMgS6w-ghc8.10.7.a"; sourceTree = ""; }; 5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = ""; }; @@ -249,6 +250,7 @@ 5CA059BD279559F40002BEB4 = { isa = PBXGroup; children = ( + 5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */, 5C764E5C279C70B7000C6508 /* Libraries */, 5CA059C2279559F40002BEB4 /* Shared */, 5CA059D1279559F40002BEB4 /* macOS */, @@ -723,13 +725,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "$(PRODUCT_NAME) needs camera access to scan QR codes to connect to other app users"; + INFOPLIST_FILE = "SimpleX--iOS--Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other app users"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -764,13 +768,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSCameraUsageDescription = "$(PRODUCT_NAME) needs camera access to scan QR codes to connect to other app users"; + INFOPLIST_FILE = "SimpleX--iOS--Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "SimpleX needs camera access to scan QR codes to connect to other app users"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;