From 1150c042984e64febb6b751ad15299cf838a10f1 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Thu, 24 Feb 2022 17:16:41 +0000 Subject: [PATCH] ios: process commands and messages asynchronously, on the background thread (#367) * ios: process commands and messages asynchronously, on the background thread * move model updates to main thread --- apps/ios/Shared/Model/ChatModel.swift | 2 +- apps/ios/Shared/Model/NtfManager.swift | 2 +- apps/ios/Shared/Model/SimpleXAPI.swift | 119 +++++++++--------- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 16 ++- apps/ios/Shared/Views/Chat/ChatView.swift | 16 ++- .../Views/ChatList/ChatListNavLink.swift | 42 ++++--- .../Shared/Views/ChatList/ChatListView.swift | 19 +-- .../Views/NewChat/ConnectContactView.swift | 14 ++- .../Shared/Views/NewChat/NewChatButton.swift | 16 +-- apps/ios/Shared/Views/TerminalView.swift | 10 +- .../Views/UserSettings/SettingsButton.swift | 13 +- .../Views/UserSettings/UserAddress.swift | 27 ++-- .../Views/UserSettings/UserProfile.swift | 18 +-- apps/ios/SimpleX.xcodeproj/project.pbxproj | 52 ++++---- 14 files changed, 210 insertions(+), 156 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 6bf6168968..3049b583f0 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -97,7 +97,7 @@ final class ChatModel: ObservableObject { if case .rcvNew = cItem.meta.itemStatus { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if self.chatId == cInfo.id { - SimpleX.markChatItemRead(cInfo, cItem) + Task { await SimpleX.markChatItemRead(cInfo, cItem) } } } } diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 073c91c0d2..5459d9b673 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -37,7 +37,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { if content.categoryIdentifier == ntfCategoryContactRequest && response.actionIdentifier == ntfActionAccept, let chatId = content.userInfo["chatId"] as? String, case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo { - acceptContactRequest(contactRequest) + Task { await acceptContactRequest(contactRequest) } } else { chatModel.chatId = content.targetContentIdentifier } diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 0cccd292e4..170806424c 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -232,10 +232,10 @@ enum TerminalItem: Identifiable { } } -func chatSendCmd(_ cmd: ChatCommand) throws -> ChatResponse { +func chatSendCmdSync(_ cmd: ChatCommand) -> ChatResponse { var c = cmd.cmdString.cString(using: .utf8)! logger.debug("chatSendCmd \(cmd.cmdType)") - let resp = chatResponse(chat_send_cmd(getChatCtrl(), &c)!) + let resp = chatResponse(chat_send_cmd(getChatCtrl(), &c)) logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)") if case let .response(_, json) = resp { logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)") @@ -247,13 +247,22 @@ func chatSendCmd(_ cmd: ChatCommand) throws -> ChatResponse { return resp } -func chatRecvMsg() throws -> ChatResponse { - chatResponse(chat_recv_msg(getChatCtrl())!) +func chatSendCmd(_ cmd: ChatCommand) async -> ChatResponse { + await withCheckedContinuation { cont in + cont.resume(returning: chatSendCmdSync(cmd)) + } +} + +func chatRecvMsg() async -> ChatResponse { + await withCheckedContinuation { cont in + let resp = chatResponse(chat_recv_msg(getChatCtrl())!) + cont.resume(returning: resp) + } } func apiGetActiveUser() throws -> User? { let _ = getChatCtrl() - let r = try chatSendCmd(.showActiveUser) + let r = chatSendCmdSync(.showActiveUser) switch r { case let .activeUser(user): return user case .chatCmdError(.error(.noActiveUser)): return nil @@ -262,43 +271,43 @@ func apiGetActiveUser() throws -> User? { } func apiCreateActiveUser(_ p: Profile) throws -> User { - let r = try chatSendCmd(.createActiveUser(profile: p)) + let r = chatSendCmdSync(.createActiveUser(profile: p)) if case let .activeUser(user) = r { return user } throw r } func apiStartChat() throws { - let r = try chatSendCmd(.startChat) + let r = chatSendCmdSync(.startChat) if case .chatStarted = r { return } throw r } func apiGetChats() throws -> [Chat] { - let r = try chatSendCmd(.apiGetChats) + let r = chatSendCmdSync(.apiGetChats) if case let .apiChats(chats) = r { return chats.map { Chat.init($0) } } throw r } -func apiGetChat(type: ChatType, id: Int64) throws -> Chat { - let r = try chatSendCmd(.apiGetChat(type: type, id: id)) +func apiGetChat(type: ChatType, id: Int64) async throws -> Chat { + let r = await chatSendCmd(.apiGetChat(type: type, id: id)) if case let .apiChat(chat) = r { return Chat.init(chat) } throw r } -func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) throws -> ChatItem { - let r = try chatSendCmd(.apiSendMessage(type: type, id: id, msg: msg)) +func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) async throws -> ChatItem { + let r = await chatSendCmd(.apiSendMessage(type: type, id: id, msg: msg)) if case let .newChatItem(aChatItem) = r { return aChatItem.chatItem } throw r } -func apiAddContact() throws -> String { - let r = try chatSendCmd(.addContact) +func apiAddContact() async throws -> String { + let r = await chatSendCmd(.addContact) if case let .invitation(connReqInvitation) = r { return connReqInvitation } throw r } -func apiConnect(connReq: String) throws { - let r = try chatSendCmd(.connect(connReq: connReq)) +func apiConnect(connReq: String) async throws { + let r = await chatSendCmd(.connect(connReq: connReq)) switch r { case .sentConfirmation: return case .sentInvitation: return @@ -306,14 +315,14 @@ func apiConnect(connReq: String) throws { } } -func apiDeleteChat(type: ChatType, id: Int64) throws { - let r = try chatSendCmd(.apiDeleteChat(type: type, id: id)) +func apiDeleteChat(type: ChatType, id: Int64) async throws { + let r = await chatSendCmd(.apiDeleteChat(type: type, id: id)) if case .contactDeleted = r { return } throw r } -func apiUpdateProfile(profile: Profile) throws -> Profile? { - let r = try chatSendCmd(.updateProfile(profile: profile)) +func apiUpdateProfile(profile: Profile) async throws -> Profile? { + let r = await chatSendCmd(.updateProfile(profile: profile)) switch r { case .userProfileNoChange: return nil case let .userProfileUpdated(_, toProfile): return toProfile @@ -321,20 +330,20 @@ func apiUpdateProfile(profile: Profile) throws -> Profile? { } } -func apiCreateUserAddress() throws -> String { - let r = try chatSendCmd(.createMyAddress) +func apiCreateUserAddress() async throws -> String { + let r = await chatSendCmd(.createMyAddress) if case let .userContactLinkCreated(connReq) = r { return connReq } throw r } -func apiDeleteUserAddress() throws { - let r = try chatSendCmd(.deleteMyAddress) +func apiDeleteUserAddress() async throws { + let r = await chatSendCmd(.deleteMyAddress) if case .userContactLinkDeleted = r { return } throw r } -func apiGetUserAddress() throws -> String? { - let r = try chatSendCmd(.showMyAddress) +func apiGetUserAddress() async throws -> String? { + let r = await chatSendCmd(.showMyAddress) switch r { case let .userContactLink(connReq): return connReq @@ -344,59 +353,59 @@ func apiGetUserAddress() throws -> String? { } } -func apiAcceptContactRequest(contactReqId: Int64) throws -> Contact { - let r = try chatSendCmd(.apiAcceptContact(contactReqId: contactReqId)) +func apiAcceptContactRequest(contactReqId: Int64) async throws -> Contact { + let r = await chatSendCmd(.apiAcceptContact(contactReqId: contactReqId)) if case let .acceptingContactRequest(contact) = r { return contact } throw r } -func apiRejectContactRequest(contactReqId: Int64) throws { - let r = try chatSendCmd(.apiRejectContact(contactReqId: contactReqId)) +func apiRejectContactRequest(contactReqId: Int64) async throws { + let r = await chatSendCmd(.apiRejectContact(contactReqId: contactReqId)) if case .contactRequestRejected = r { return } throw r } -func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) throws { - let r = try chatSendCmd(.apiChatRead(type: type, id: id, itemRange: itemRange)) +func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws { + let r = await chatSendCmd(.apiChatRead(type: type, id: id, itemRange: itemRange)) if case .cmdOk = r { return } throw r } -func acceptContactRequest(_ contactRequest: UserContactRequest) { +func acceptContactRequest(_ contactRequest: UserContactRequest) async { do { - let contact = try apiAcceptContactRequest(contactReqId: contactRequest.apiId) + let contact = try await apiAcceptContactRequest(contactReqId: contactRequest.apiId) let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: []) - ChatModel.shared.replaceChat(contactRequest.id, chat) + DispatchQueue.main.async { ChatModel.shared.replaceChat(contactRequest.id, chat) } } catch let error { logger.error("acceptContactRequest error: \(error.localizedDescription)") } } -func rejectContactRequest(_ contactRequest: UserContactRequest) { +func rejectContactRequest(_ contactRequest: UserContactRequest) async { do { - try apiRejectContactRequest(contactReqId: contactRequest.apiId) - ChatModel.shared.removeChat(contactRequest.id) + try await apiRejectContactRequest(contactReqId: contactRequest.apiId) + DispatchQueue.main.async { ChatModel.shared.removeChat(contactRequest.id) } } catch let error { logger.error("rejectContactRequest: \(error.localizedDescription)") } } -func markChatRead(_ chat: Chat) { +func markChatRead(_ chat: Chat) async { do { let minItemId = chat.chatStats.minUnreadItemId let itemRange = (minItemId, chat.chatItems.last?.id ?? minItemId) let cInfo = chat.chatInfo - try apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange) - ChatModel.shared.markChatItemsRead(cInfo) + try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange) + DispatchQueue.main.async { ChatModel.shared.markChatItemsRead(cInfo) } } catch { logger.error("markChatRead apiChatRead error: \(error.localizedDescription)") } } -func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) { +func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { do { - try apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id)) - ChatModel.shared.markChatItemRead(cInfo, cItem) + try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id)) + DispatchQueue.main.async { ChatModel.shared.markChatItemRead(cInfo, cItem) } } catch { logger.error("markChatItemRead apiChatRead error: \(error.localizedDescription)") } @@ -411,7 +420,7 @@ func initializeChat() { } class ChatReceiver { - private var receiveLoop: DispatchWorkItem? + private var receiveLoop: Task? private var receiveMessages = true private var _lastMsgTime = Date.now @@ -424,18 +433,16 @@ class ChatReceiver { receiveMessages = true _lastMsgTime = .now if receiveLoop != nil { return } - let loop = DispatchWorkItem(qos: .default, flags: []) { - while self.receiveMessages { - do { - processReceivedMsg(try chatRecvMsg()) - self._lastMsgTime = .now - } catch { - logger.error("ChatReceiver.start chatRecvMsg error: \(error.localizedDescription)") - } - } + receiveLoop = Task { await receiveMsgLoop() } + } + + func receiveMsgLoop() async { + let msg = await chatRecvMsg() + self._lastMsgTime = .now + processReceivedMsg(msg) + if self.receiveMessages { + await receiveMsgLoop() } - receiveLoop = loop - DispatchQueue.global().async(execute: loop) } func stop() { diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 078f05c530..7e0905913f 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -63,12 +63,16 @@ struct ChatInfoView: View { title: Text("Delete contact?"), message: Text("Contact and all messages will be deleted"), primaryButton: .destructive(Text("Delete")) { - do { - try apiDeleteChat(type: .direct, id: contact.apiId) - chatModel.removeChat(contact.id) - showChatInfo = false - } catch let error { - logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)") + Task { + do { + try await apiDeleteChat(type: .direct, id: contact.apiId) + DispatchQueue.main.async { + chatModel.removeChat(contact.id) + showChatInfo = false + } + } catch let error { + logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)") + } } }, secondaryButton: .cancel() diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index cdaa1905da..540a88af62 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -107,17 +107,21 @@ struct ChatView: View { func markAllRead() { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { if chatModel.chatId == chat.id { - markChatRead(chat) + Task { await markChatRead(chat) } } } } func sendMessage(_ msg: String) { - do { - let chatItem = try apiSendMessage(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, msg: .text(msg)) - chatModel.addChatItem(chat.chatInfo, chatItem) - } catch { - logger.error("ChatView.sendMessage apiSendMessage error: \(error.localizedDescription)") + Task { + do { + let chatItem = try await apiSendMessage(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, msg: .text(msg)) + DispatchQueue.main.async { + chatModel.addChatItem(chat.chatInfo, chatItem) + } + } catch { + logger.error("ChatView.sendMessage apiSendMessage error: \(error.localizedDescription)") + } } } } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 75da1fea98..e5f2ed3718 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -27,13 +27,17 @@ struct ChatListNavLink: View { private func chatView() -> some View { ChatView(chat: chat) .onAppear { - do { - let cInfo = chat.chatInfo - let chat = try apiGetChat(type: cInfo.chatType, id: cInfo.apiId) - chatModel.updateChatInfo(chat.chatInfo) - chatModel.chatItems = chat.chatItems - } catch { - logger.error("ChatListNavLink.chatView apiGetChatItems error: \(error.localizedDescription)") + Task { + do { + let cInfo = chat.chatInfo + let chat = try await apiGetChat(type: cInfo.chatType, id: cInfo.apiId) + DispatchQueue.main.async { + chatModel.updateChatInfo(chat.chatInfo) + chatModel.chatItems = chat.chatItems + } + } catch { + logger.error("ChatListNavLink.chatView apiGetChatItems error: \(error.localizedDescription)") + } } } } @@ -86,7 +90,7 @@ struct ChatListNavLink: View { private func markReadButton() -> some View { Button { - markChatRead(chat) + Task { await markChatRead(chat) } } label: { Label("Read", systemImage: "checkmark") } @@ -96,7 +100,7 @@ struct ChatListNavLink: View { private func contactRequestNavLink(_ contactRequest: UserContactRequest) -> some View { ContactRequestView(contactRequest: contactRequest) .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button { acceptContactRequest(contactRequest) } + Button { Task { await acceptContactRequest(contactRequest) } } label: { Label("Accept", systemImage: "checkmark") } .tint(Color.accentColor) Button(role: .destructive) { @@ -108,8 +112,8 @@ struct ChatListNavLink: View { .frame(height: 80) .onTapGesture { showContactRequestDialog = true } .confirmationDialog("Connection request", isPresented: $showContactRequestDialog, titleVisibility: .visible) { - Button("Accept contact") { acceptContactRequest(contactRequest) } - Button("Reject contact (sender NOT notified)") { rejectContactRequest(contactRequest) } + Button("Accept contact") { Task { await acceptContactRequest(contactRequest) } } + Button("Reject contact (sender NOT notified)") { Task { await rejectContactRequest(contactRequest) } } } } @@ -118,11 +122,15 @@ struct ChatListNavLink: View { title: Text("Delete contact?"), message: Text("Contact and all messages will be deleted"), primaryButton: .destructive(Text("Delete")) { - do { - try apiDeleteChat(type: .direct, id: contact.apiId) - chatModel.removeChat(contact.id) - } catch let error { - logger.error("ChatListNavLink.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)") + Task { + do { + try await apiDeleteChat(type: .direct, id: contact.apiId) + DispatchQueue.main.async { + chatModel.removeChat(contact.id) + } + } catch let error { + logger.error("ChatListNavLink.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)") + } } }, secondaryButton: .cancel() @@ -141,7 +149,7 @@ struct ChatListNavLink: View { title: Text("Reject contact request"), message: Text("The sender will NOT be notified"), primaryButton: .destructive(Text("Reject")) { - rejectContactRequest(contactRequest) + Task { await rejectContactRequest(contactRequest) } }, secondaryButton: .cancel() ) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 4f5d044716..fe1e24ea39 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -80,19 +80,22 @@ struct ChatListView: View { logger.debug("ChatListView.connectViaUrlAlert path: \(path)") if (path == "/contact" || path == "/invitation") { path.removeFirst() + let action = path let link = url.absoluteString.replacingOccurrences(of: "///\(path)", with: "/\(path)") return Alert( - title: Text("Connect via \(path) link?"), + title: Text("Connect via \(action) link?"), message: Text("Your profile will be sent to the contact that you received this link from: \(link)"), primaryButton: .default(Text("Connect")) { DispatchQueue.main.async { - do { - try apiConnect(connReq: link) - connectionReqSentAlert(path == "contact" ? .contact : .invitation) - } catch { - let err = error.localizedDescription - AlertManager.shared.showAlertMsg(title: "Connection error", message: err) - logger.debug("ChatListView.connectViaUrlAlert: apiConnect error: \(err)") + Task { + do { + try await apiConnect(connReq: link) + connectionReqSentAlert(action == "contact" ? .contact : .invitation) + } catch { + let err = error.localizedDescription + AlertManager.shared.showAlertMsg(title: "Connection error", message: err) + logger.debug("ChatListView.connectViaUrlAlert: apiConnect error: \(err)") + } } } }, diff --git a/apps/ios/Shared/Views/NewChat/ConnectContactView.swift b/apps/ios/Shared/Views/NewChat/ConnectContactView.swift index 024c310434..b6a118b10f 100644 --- a/apps/ios/Shared/Views/NewChat/ConnectContactView.swift +++ b/apps/ios/Shared/Views/NewChat/ConnectContactView.swift @@ -33,12 +33,14 @@ struct ConnectContactView: View { func processQRCode(_ resp: Result) { switch resp { case let .success(r): - do { - try apiConnect(connReq: r.string) - completed(nil) - } catch { - logger.error("ConnectContactView.processQRCode apiConnect error: \(error.localizedDescription)") - completed(error) + Task { + do { + try await apiConnect(connReq: r.string) + completed(nil) + } catch { + logger.error("ConnectContactView.processQRCode apiConnect error: \(error.localizedDescription)") + completed(error) + } } case let .failure(e): logger.error("ConnectContactView.processQRCode QR code error: \(e.localizedDescription)") diff --git a/apps/ios/Shared/Views/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/NewChat/NewChatButton.swift index b389f9c47a..db8e1d9f39 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatButton.swift @@ -35,14 +35,16 @@ struct NewChatButton: View { } func addContactAction() { - do { - connReqInvitation = try apiAddContact() - addContact = true - } catch { - DispatchQueue.global().async { - connectionErrorAlert(error) + Task { + do { + connReqInvitation = try await apiAddContact() + addContact = true + } catch { + DispatchQueue.global().async { + connectionErrorAlert(error) + } + logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") } - logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") } } diff --git a/apps/ios/Shared/Views/TerminalView.swift b/apps/ios/Shared/Views/TerminalView.swift index 9d561a9c26..60e54807a9 100644 --- a/apps/ios/Shared/Views/TerminalView.swift +++ b/apps/ios/Shared/Views/TerminalView.swift @@ -73,13 +73,11 @@ struct TerminalView: View { func sendMessage(_ cmdStr: String) { let cmd = ChatCommand.string(cmdStr) DispatchQueue.global().async { - inProgress = true - do { - let _ = try chatSendCmd(cmd) - } catch { - logger.error("TerminalView.sendMessage chatSendCmd error: \(error.localizedDescription)") + Task { + inProgress = true + _ = await chatSendCmd(cmd) + inProgress = false } - inProgress = false } } } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsButton.swift b/apps/ios/Shared/Views/UserSettings/SettingsButton.swift index 7bfc2eec49..4d9818278d 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsButton.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsButton.swift @@ -19,10 +19,15 @@ struct SettingsButton: View { .sheet(isPresented: $showSettings, content: { SettingsView(showSettings: $showSettings) .onAppear { - do { - chatModel.userAddress = try apiGetUserAddress() - } catch { - logger.error("SettingsButton apiGetUserAddress error: \(error.localizedDescription)") + Task { + do { + let userAddress = try await apiGetUserAddress() + DispatchQueue.main.async { + chatModel.userAddress = userAddress + } + } catch { + logger.error("SettingsButton apiGetUserAddress error: \(error.localizedDescription)") + } } } }) diff --git a/apps/ios/Shared/Views/UserSettings/UserAddress.swift b/apps/ios/Shared/Views/UserSettings/UserAddress.swift index 8b467316cb..e15bd167c1 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddress.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddress.swift @@ -35,11 +35,15 @@ struct UserAddress: View { title: Text("Delete address?"), message: Text("All your contacts will remain connected"), primaryButton: .destructive(Text("Delete")) { - do { - try apiDeleteUserAddress() - chatModel.userAddress = nil - } catch let error { - logger.error("UserAddress apiDeleteUserAddress: \(error.localizedDescription)") + Task { + do { + try await apiDeleteUserAddress() + DispatchQueue.main.async { + chatModel.userAddress = nil + } + } catch let error { + logger.error("UserAddress apiDeleteUserAddress: \(error.localizedDescription)") + } } }, secondaryButton: .cancel() ) @@ -48,10 +52,15 @@ struct UserAddress: View { .frame(maxWidth: .infinity) } else { Button { - do { - chatModel.userAddress = try apiCreateUserAddress() - } catch let error { - logger.error("UserAddress apiCreateUserAddress: \(error.localizedDescription)") + Task { + do { + let userAddress = try await apiCreateUserAddress() + DispatchQueue.main.async { + chatModel.userAddress = userAddress + } + } catch let error { + logger.error("UserAddress apiCreateUserAddress: \(error.localizedDescription)") + } } } label: { Label("Create address", systemImage: "qrcode") } .frame(maxWidth: .infinity) diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index 2dd5575033..79b33d03bc 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -62,15 +62,19 @@ struct UserProfile: View { } func saveProfile() { - do { - if let newProfile = try apiUpdateProfile(profile: profile) { - chatModel.currentUser?.profile = newProfile - profile = newProfile + Task { + do { + if let newProfile = try await apiUpdateProfile(profile: profile) { + DispatchQueue.main.async { + chatModel.currentUser?.profile = newProfile + profile = newProfile + } + } + } catch { + logger.error("UserProfile apiUpdateProfile error: \(error.localizedDescription)") } - } catch { - logger.error("UserProfile apiUpdateProfile error: \(error.localizedDescription)") + editProfile = false } - editProfile = false } } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index e06ad337fe..b56a9f7ae0 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -13,11 +13,16 @@ 5C116CDD27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; 5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; 5C1A4C1F27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; }; - 5C27CFFE27C61CAB00DD6182 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFF927C61CAB00DD6182 /* libgmp.a */; }; - 5C27CFFF27C61CAB00DD6182 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFA27C61CAB00DD6182 /* libgmpxx.a */; }; - 5C27D00027C61CAB00DD6182 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFB27C61CAB00DD6182 /* libffi.a */; }; - 5C27D00127C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFC27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a */; }; - 5C27D00227C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27CFFD27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a */; }; + 5C27D00827C7D8B500DD6182 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00327C7D8B500DD6182 /* libgmpxx.a */; }; + 5C27D00927C7D8B500DD6182 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00327C7D8B500DD6182 /* libgmpxx.a */; }; + 5C27D00A27C7D8B500DD6182 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00427C7D8B500DD6182 /* libgmp.a */; }; + 5C27D00B27C7D8B500DD6182 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00427C7D8B500DD6182 /* libgmp.a */; }; + 5C27D00C27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */; }; + 5C27D00D27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */; }; + 5C27D00E27C7D8B500DD6182 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00627C7D8B500DD6182 /* libffi.a */; }; + 5C27D00F27C7D8B500DD6182 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00627C7D8B500DD6182 /* libffi.a */; }; + 5C27D01027C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */; }; + 5C27D01127C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */; }; 5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; }; 5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; }; 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; }; @@ -118,11 +123,11 @@ 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = ""; }; 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = ""; }; 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = ""; }; - 5C27CFF927C61CAB00DD6182 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C27CFFA27C61CAB00DD6182 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5C27CFFB27C61CAB00DD6182 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C27CFFC27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a"; sourceTree = ""; }; - 5C27CFFD27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a"; sourceTree = ""; }; + 5C27D00327C7D8B500DD6182 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C27D00427C7D8B500DD6182 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a"; sourceTree = ""; }; + 5C27D00627C7D8B500DD6182 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a"; sourceTree = ""; }; 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = ""; }; 5C2E260927A2C63500F70299 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; @@ -178,14 +183,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5C27D00827C7D8B500DD6182 /* libgmpxx.a in Frameworks */, + 5C27D01027C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */, + 5C27D00A27C7D8B500DD6182 /* libgmp.a in Frameworks */, 5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */, - 5C27D00027C61CAB00DD6182 /* libffi.a in Frameworks */, - 5C27D00227C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a in Frameworks */, - 5C27CFFF27C61CAB00DD6182 /* libgmpxx.a in Frameworks */, 5C764E83279C748B000C6508 /* libz.tbd in Frameworks */, - 5C27CFFE27C61CAB00DD6182 /* libgmp.a in Frameworks */, + 5C27D00E27C7D8B500DD6182 /* libffi.a in Frameworks */, + 5C27D00C27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */, 5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */, - 5C27D00127C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -193,7 +198,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5C27D00927C7D8B500DD6182 /* libgmpxx.a in Frameworks */, + 5C27D00F27C7D8B500DD6182 /* libffi.a in Frameworks */, 5C764E85279C748C000C6508 /* libz.tbd in Frameworks */, + 5C27D00B27C7D8B500DD6182 /* libgmp.a in Frameworks */, + 5C27D01127C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a in Frameworks */, + 5C27D00D27C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a in Frameworks */, 5C764E84279C748C000C6508 /* libiconv.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -246,11 +256,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C27CFFB27C61CAB00DD6182 /* libffi.a */, - 5C27CFF927C61CAB00DD6182 /* libgmp.a */, - 5C27CFFA27C61CAB00DD6182 /* libgmpxx.a */, - 5C27CFFC27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP-ghc8.10.7.a */, - 5C27CFFD27C61CAB00DD6182 /* libHSsimplex-chat-1.2.1-GdoRfDZWUHsJU1nzGNbZpP.a */, + 5C27D00627C7D8B500DD6182 /* libffi.a */, + 5C27D00427C7D8B500DD6182 /* libgmp.a */, + 5C27D00327C7D8B500DD6182 /* libgmpxx.a */, + 5C27D00727C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY-ghc8.10.7.a */, + 5C27D00527C7D8B500DD6182 /* libHSsimplex-chat-1.2.1-KSWVFEZPyZUBOUtDs8BKKY.a */, ); path = Libraries; sourceTree = ""; @@ -816,7 +826,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ""; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; MARKETING_VERSION = 0.3.1; @@ -856,7 +865,6 @@ "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ""; "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(PROJECT_DIR)/Libraries/ios"; "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(PROJECT_DIR)/Libraries/sim"; MARKETING_VERSION = 0.3.1;