diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 75c9b4d60..fce1705c6 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -220,7 +220,7 @@ func loadChat(chat: Chat, search: String = "") { } } -func apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent) async throws -> ChatItem { +func apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent) async -> ChatItem? { let chatModel = ChatModel.shared let cmd: ChatCommand = .apiSendMessage(type: type, id: id, file: file, quotedItemId: quotedItemId, msg: msg) let r: ChatResponse @@ -233,14 +233,27 @@ func apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int6 chatModel.messageDelivery[cItem.id] = endTask return cItem } + if !networkErrorAlert(r) { + sendMessageErrorAlert(r) + } endTask() + return nil } else { r = await chatSendCmd(cmd, bgDelay: msgDelay) if case let .newChatItem(aChatItem) = r { return aChatItem.chatItem } + sendMessageErrorAlert(r) + return nil } - throw r +} + +private func sendMessageErrorAlert(_ r: ChatResponse) { + logger.error("apiSendMessage error: \(String(describing: r))") + AlertManager.shared.showAlertMsg( + title: "Error sending message", + message: "Error: \(String(describing: r))" + ) } func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent) async throws -> ChatItem { @@ -335,13 +348,14 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws - throw r } -func apiAddContact() throws -> String { +func apiAddContact() -> String? { let r = chatSendCmdSync(.addContact, bgTask: false) if case let .invitation(connReqInvitation) = r { return connReqInvitation } - throw r + connectionErrorAlert(r) + return nil } -func apiConnect(connReq: String) async throws -> ConnReqType? { +func apiConnect(connReq: String) async -> ConnReqType? { let r = await chatSendCmd(.connect(connReq: connReq)) let am = AlertManager.shared switch r { @@ -359,18 +373,6 @@ func apiConnect(connReq: String) async throws -> ConnReqType? { message: "Please check that you used the correct link or ask your contact to send you another one." ) return nil - case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))): - am.showAlertMsg( - title: "Connection timeout", - message: "Please check your network connection and try again." - ) - return nil - case .chatCmdError(.errorAgent(.BROKER(.NETWORK))): - am.showAlertMsg( - title: "Connection error", - message: "Please check your network connection and try again." - ) - return nil case .chatCmdError(.errorAgent(.SMP(.AUTH))): am.showAlertMsg( title: "Connection error (AUTH)", @@ -384,10 +386,19 @@ func apiConnect(connReq: String) async throws -> ConnReqType? { message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))." ) return nil - } else { - throw r } - default: throw r + default: () + } + connectionErrorAlert(r) + return nil +} + +private func connectionErrorAlert(_ r: ChatResponse) { + if !networkErrorAlert(r) { + AlertManager.shared.showAlertMsg( + title: "Connection error", + message: "Error: \(String(describing: r))" + ) } } @@ -404,8 +415,20 @@ func deleteChat(_ chat: Chat) async { let cInfo = chat.chatInfo try await apiDeleteChat(type: cInfo.chatType, id: cInfo.apiId) DispatchQueue.main.async { ChatModel.shared.removeChat(cInfo.id) } - } catch { + } catch let error { logger.error("deleteChat apiDeleteChat error: \(responseError(error))") + switch error as? ChatResponse { + case let .chatCmdError(.error(.contactGroups(contact, groupNames))): + AlertManager.shared.showAlertMsg( + title: "Can't delete contact!", + message: "Contact \(contact.displayName) cannot be deleted, they are a member of the group(s) \(groupNames.joined(separator: ", "))." + ) + default: + AlertManager.shared.showAlertMsg( + title: "Error deleting chat!", + message: "Error: \(responseError(error))" + ) + } } } @@ -469,10 +492,24 @@ func apiGetUserAddress() throws -> String? { } } -func apiAcceptContactRequest(contactReqId: Int64) async throws -> Contact { +func apiAcceptContactRequest(contactReqId: Int64) async -> Contact? { let r = await chatSendCmd(.apiAcceptContact(contactReqId: contactReqId)) + let am = AlertManager.shared + if case let .acceptingContactRequest(contact) = r { return contact } - throw r + if case .chatCmdError(.errorAgent(.SMP(.AUTH))) = r { + am.showAlertMsg( + title: "Connection error (AUTH)", + message: "Sender may have deleted the connection request." + ) + } else if !networkErrorAlert(r) { + logger.error("apiAcceptContactRequest error: \(String(describing: r))") + am.showAlertMsg( + title: "Error accepting contact request", + message: "Error: \(String(describing: r))" + ) + } + return nil } func apiRejectContactRequest(contactReqId: Int64) async throws { @@ -486,27 +523,54 @@ func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async thr } func receiveFile(fileId: Int64) async { - do { - let chatItem = try await apiReceiveFile(fileId: fileId) + if let chatItem = await apiReceiveFile(fileId: fileId) { DispatchQueue.main.async { chatItemSimpleUpdate(chatItem) } - } catch let error { - logger.error("receiveFile error: \(responseError(error))") } } -func apiReceiveFile(fileId: Int64) async throws -> AChatItem { +func apiReceiveFile(fileId: Int64) async -> AChatItem? { let r = await chatSendCmd(.receiveFile(fileId: fileId)) + let am = AlertManager.shared if case let .rcvFileAccepted(chatItem) = r { return chatItem } - throw r + if case .rcvFileAcceptedSndCancelled = r { + am.showAlertMsg( + title: "Cannot receive file", + message: "Sender cancelled file transfer." + ) + } else if !networkErrorAlert(r) { + logger.error("apiReceiveFile error: \(String(describing: r))") + am.showAlertMsg( + title: "Error receiving file", + message: "Error: \(String(describing: r))" + ) + } + return nil +} + +func networkErrorAlert(_ r: ChatResponse) -> Bool { + let am = AlertManager.shared + switch r { + case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))): + am.showAlertMsg( + title: "Connection timeout", + message: "Please check your network connection and try again." + ) + return true + case .chatCmdError(.errorAgent(.BROKER(.NETWORK))): + am.showAlertMsg( + title: "Connection error", + message: "Please check your network connection and try again." + ) + return true + default: + return false + } } func acceptContactRequest(_ contactRequest: UserContactRequest) async { - do { - let contact = try await apiAcceptContactRequest(contactReqId: contactRequest.apiId) + if let contact = await apiAcceptContactRequest(contactReqId: contactRequest.apiId) { let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: []) DispatchQueue.main.async { ChatModel.shared.replaceChat(contactRequest.id, chat) } - } catch let error { - logger.error("acceptContactRequest error: \(responseError(error))") } } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 667ae0e59..3f5274b17 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -56,10 +56,18 @@ struct ChatInfoView: View { enum ChatInfoViewAlert: Identifiable { case deleteContactAlert + case contactGroupsAlert(groupNames: [GroupName]) case clearChatAlert case networkStatusAlert - var id: ChatInfoViewAlert { get { self } } + var id: String { + switch self { + case .deleteContactAlert: return "deleteContactAlert" + case .contactGroupsAlert: return "contactGroupsAlert" + case .clearChatAlert: return "clearChatAlert" + case .networkStatusAlert: return "networkStatusAlert" + } + } } var body: some View { @@ -111,6 +119,7 @@ struct ChatInfoView: View { .alert(item: $alert) { alertItem in switch(alertItem) { case .deleteContactAlert: return deleteContactAlert() + case let .contactGroupsAlert(groupNames): return contactGroupsAlert(groupNames) case .clearChatAlert: return clearChatAlert() case .networkStatusAlert: return networkStatusAlert() } @@ -221,6 +230,9 @@ struct ChatInfoView: View { } } catch let error { logger.error("deleteContactAlert apiDeleteChat error: \(error.localizedDescription)") + if case let .chatCmdError(.error(.contactGroups(_, groupNames))) = error as? ChatResponse { + alert = .contactGroupsAlert(groupNames: groupNames) + } } } }, @@ -228,6 +240,13 @@ struct ChatInfoView: View { ) } + private func contactGroupsAlert(_ groupNames: [GroupName]) -> Alert { + Alert( + title: Text("Can't delete contact!"), + message: Text("Contact \(contact.displayName) cannot be deleted, they are a member of the group(s) \(groupNames.joined(separator: ", ")).") + ) + } + private func clearChatAlert() -> Alert { Alert( title: Text("Clear conversation?"), diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index c97719dc1..eb0120204 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -284,10 +284,10 @@ struct ComposeView: View { logger.debug("ChatView sendMessage") Task { logger.debug("ChatView sendMessage: in Task") - do { - switch composeState.contextItem { - case let .editingItem(chatItem: ei): - if let oldMsgContent = ei.content.msgContent { + switch composeState.contextItem { + case let .editingItem(chatItem: ei): + if let oldMsgContent = ei.content.msgContent { + do { let chatItem = try await apiUpdateChatItem( type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, @@ -297,52 +297,53 @@ struct ComposeView: View { DispatchQueue.main.async { let _ = self.chatModel.upsertChatItem(self.chat.chatInfo, chatItem) } - } - default: - var mc: MsgContent? = nil - var file: String? = nil - switch (composeState.preview) { - case .noPreview: - mc = .text(composeState.message) - case .linkPreview: - mc = checkLinkPreview() - case let .imagePreview(imagePreview: image): - if let uiImage = chosenImage, - let savedFile = saveImage(uiImage) { - mc = .image(text: composeState.message, image: image) - file = savedFile - } - case .filePreview: - if let fileURL = chosenFile, - let savedFile = saveFileFromURL(fileURL) { - mc = .file(composeState.message) - file = savedFile - } - } - - var quotedItemId: Int64? = nil - switch (composeState.contextItem) { - case let .quotedItem(chatItem: quotedItem): - quotedItemId = quotedItem.id - default: - quotedItemId = nil - } - if let mc = mc { - let chatItem = try await apiSendMessage( - type: chat.chatInfo.chatType, - id: chat.chatInfo.apiId, - file: file, - quotedItemId: quotedItemId, - msg: mc - ) - chatModel.addChatItem(chat.chatInfo, chatItem) + } catch { + clearState() + logger.error("ChatView.sendMessage error: \(error.localizedDescription)") + AlertManager.shared.showAlertMsg(title: "Error updating message", message: "Error: \(responseError(error))") } } - clearState() - } catch { - clearState() - logger.error("ChatView.sendMessage error: \(error.localizedDescription)") + default: + var mc: MsgContent? = nil + var file: String? = nil + switch (composeState.preview) { + case .noPreview: + mc = .text(composeState.message) + case .linkPreview: + mc = checkLinkPreview() + case let .imagePreview(imagePreview: image): + if let uiImage = chosenImage, + let savedFile = saveImage(uiImage) { + mc = .image(text: composeState.message, image: image) + file = savedFile + } + case .filePreview: + if let fileURL = chosenFile, + let savedFile = saveFileFromURL(fileURL) { + mc = .file(composeState.message) + file = savedFile + } + } + + var quotedItemId: Int64? = nil + switch (composeState.contextItem) { + case let .quotedItem(chatItem: quotedItem): + quotedItemId = quotedItem.id + default: + quotedItemId = nil + } + if let mc = mc, + let chatItem = await apiSendMessage( + type: chat.chatInfo.chatType, + id: chat.chatInfo.apiId, + file: file, + quotedItemId: quotedItemId, + msg: mc + ) { + chatModel.addChatItem(chat.chatInfo, chatItem) + } } + clearState() } } diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 2ab0fc516..1ada8c240 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -128,7 +128,14 @@ struct AddGroupMembersView: View { await MainActor.run { dismiss() } if let cb = addedMembersCb { cb(selectedContacts) } } catch { - alert = .error(title: "Error adding member(s)", error: responseError(error)) + switch error as? ChatResponse { + case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))): + alert = .error(title: "Connection timeout", error: "Please check your network connection and try again.") + case .chatCmdError(.errorAgent(.BROKER(.NETWORK))): + alert = .error(title: "Connection error", error: "Please check your network connection and try again.") + default: + alert = .error(title: "Error adding member(s)", error: responseError(error)) + } } } } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 4e19412bd..226aabeb7 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -345,9 +345,15 @@ func joinGroup(_ groupId: Int64) { await deleteGroup() } } catch let error { - let err = responseError(error) - AlertManager.shared.showAlert(Alert(title: Text("Error joining group"), message: Text(err))) - logger.error("apiJoinGroup error: \(err)") + switch error as? ChatResponse { + case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))): + AlertManager.shared.showAlertMsg(title: "Connection timeout", message: "Please check your network connection and try again.") + case .chatCmdError(.errorAgent(.BROKER(.NETWORK))): + AlertManager.shared.showAlertMsg(title: "Connection error", message: "Please check your network connection and try again.") + default: + logger.error("apiJoinGroup error: \(responseError(error))") + AlertManager.shared.showAlertMsg(title: "Error joining group", message: "\(responseError(error))") + } } func deleteGroup() async { diff --git a/apps/ios/Shared/Views/NewChat/NewChatButton.swift b/apps/ios/Shared/Views/NewChat/NewChatButton.swift index e4571cd09..2fa981caf 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatButton.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatButton.swift @@ -47,14 +47,9 @@ struct NewChatButton: View { } func addContactAction() { - do { - connReq = try apiAddContact() + if let cReq = apiAddContact() { + connReq = cReq actionSheet = .createLink - } catch { - DispatchQueue.global().async { - connectionErrorAlert(error) - } - logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") } } } @@ -66,28 +61,19 @@ enum ConnReqType: Equatable { func connectViaLink(_ connectionLink: String, _ dismiss: DismissAction? = nil) { Task { - do { - let res = try await apiConnect(connReq: connectionLink) + if let connReqType = await apiConnect(connReq: connectionLink) { DispatchQueue.main.async { dismiss?() - if let connReqType = res { - connectionReqSentAlert(connReqType) - } + connectionReqSentAlert(connReqType) } - } catch { - logger.error("connectViaLink apiConnect error: \(responseError(error))") + } else { DispatchQueue.main.async { dismiss?() - connectionErrorAlert(error) } } } } -func connectionErrorAlert(_ error: Error) { - AlertManager.shared.showAlertMsg(title: "Connection error", message: "Error: \(responseError(error))") -} - func connectionReqSentAlert(_ type: ConnReqType) { AlertManager.shared.showAlertMsg( title: "Connection request sent!", diff --git a/apps/ios/Shared/Views/Onboarding/MakeConnection.swift b/apps/ios/Shared/Views/Onboarding/MakeConnection.swift index d3b46c991..81bee84d9 100644 --- a/apps/ios/Shared/Views/Onboarding/MakeConnection.swift +++ b/apps/ios/Shared/Views/Onboarding/MakeConnection.swift @@ -112,14 +112,9 @@ struct MakeConnection: View { } private func addContactAction() { - do { - connReq = try apiAddContact() + if let cReq = apiAddContact() { + connReq = cReq actionSheet = .createLink - } catch { - DispatchQueue.global().async { - connectionErrorAlert(error) - } - logger.error("NewChatButton.addContactAction apiAddContact error: \(error.localizedDescription)") } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddress.swift b/apps/ios/Shared/Views/UserSettings/UserAddress.swift index 16e4994d3..c21bfbf6b 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddress.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddress.swift @@ -7,10 +7,23 @@ // import SwiftUI +import SimpleXChat struct UserAddress: View { @EnvironmentObject var chatModel: ChatModel - @State private var deleteAddressAlert = false + @State private var alert: UserAddressAlert? + + private enum UserAddressAlert: Identifiable { + case deleteAddress + case error(title: LocalizedStringKey, error: String = "") + + var id: String { + switch self { + case .deleteAddress: return "deleteAddress" + case let .error(title, _): return "error \(title)" + } + } + } var body: some View { ScrollView { @@ -27,28 +40,10 @@ struct UserAddress: View { } .padding() - Button(role: .destructive) { deleteAddressAlert = true } label: { + Button(role: .destructive) { alert = .deleteAddress } label: { Label("Delete address", systemImage: "trash") } .padding() - .alert(isPresented: $deleteAddressAlert) { - Alert( - title: Text("Delete address?"), - message: Text("All your contacts will remain connected"), - primaryButton: .destructive(Text("Delete")) { - Task { - do { - try await apiDeleteUserAddress() - DispatchQueue.main.async { - chatModel.userAddress = nil - } - } catch let error { - logger.error("UserAddress apiDeleteUserAddress: \(error.localizedDescription)") - } - } - }, secondaryButton: .cancel() - ) - } } .frame(maxWidth: .infinity) } else { @@ -61,6 +56,14 @@ struct UserAddress: View { } } catch let error { logger.error("UserAddress apiCreateUserAddress: \(error.localizedDescription)") + switch error as? ChatResponse { + case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))): + alert = .error(title: "Connection timeout", error: "Please check your network connection and try again.") + case .chatCmdError(.errorAgent(.BROKER(.NETWORK))): + alert = .error(title: "Connection error", error: "Please check your network connection and try again.") + default: + alert = .error(title: "Error creating address", error: "Error: \(responseError(error))") + } } } } label: { Label("Create address", systemImage: "qrcode") } @@ -69,6 +72,29 @@ struct UserAddress: View { } .padding() .frame(maxHeight: .infinity, alignment: .top) + .alert(item: $alert) { alert in + switch alert { + case .deleteAddress: + return Alert( + title: Text("Delete address?"), + message: Text("All your contacts will remain connected"), + primaryButton: .destructive(Text("Delete")) { + Task { + do { + try await apiDeleteUserAddress() + DispatchQueue.main.async { + chatModel.userAddress = nil + } + } catch let error { + logger.error("UserAddress apiDeleteUserAddress: \(error.localizedDescription)") + } + } + }, secondaryButton: .cancel() + ) + case let .error(title, error): + return Alert(title: Text(title), message: Text("\(error)")) + } + } } } } diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index bfca67bef..73768a7d8 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -308,6 +308,11 @@ Calls No comment provided by engineer. + + Can't delete contact! + Can't delete contact! + No comment provided by engineer. + Can't invite contact! Can't invite contact! @@ -328,6 +333,11 @@ Cannot access keychain to save database password No comment provided by engineer. + + Cannot receive file + Cannot receive file + No comment provided by engineer. + Change database passphrase? Change database passphrase? @@ -498,6 +508,11 @@ Connection timeout No comment provided by engineer. + + Contact %@ cannot be deleted, they are a member of the group(s) %@. + Contact %@ cannot be deleted, they are a member of the group(s) %@. + No comment provided by engineer. + Contact already exists Contact already exists @@ -911,6 +926,11 @@ Enter passphrase… No comment provided by engineer. + + Error accepting contact request + Error accepting contact request + No comment provided by engineer. + Error accessing database file Error accessing database file @@ -921,6 +941,11 @@ Error adding member(s) No comment provided by engineer. + + Error creating address + Error creating address + No comment provided by engineer. + Error creating group Error creating group @@ -931,6 +956,11 @@ Error deleting chat database No comment provided by engineer. + + Error deleting chat! + Error deleting chat! + No comment provided by engineer. + Error deleting database Error deleting database @@ -971,6 +1001,11 @@ Error joining group No comment provided by engineer. + + Error receiving file + Error receiving file + No comment provided by engineer. + Error saving ICE servers Error saving ICE servers @@ -986,6 +1021,11 @@ Error saving group profile No comment provided by engineer. + + Error sending message + Error sending message + No comment provided by engineer. + Error starting chat Error starting chat @@ -996,6 +1036,11 @@ Error stopping chat No comment provided by engineer. + + Error updating message + Error updating message + No comment provided by engineer. + Error updating settings Error updating settings @@ -1928,6 +1973,16 @@ We will be adding server redundancy to prevent lost messages. Send notifications: No comment provided by engineer. + + Sender cancelled file transfer. + Sender cancelled file transfer. + No comment provided by engineer. + + + Sender may have deleted the connection request. + Sender may have deleted the connection request. + No comment provided by engineer. + Sending via Sending via diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 6cb28901b..a608cf4e4 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -308,6 +308,11 @@ Звонки No comment provided by engineer. + + Can't delete contact! + Невозможно удалить контакт! + No comment provided by engineer. + Can't invite contact! Нельзя пригласить контакт! @@ -328,6 +333,11 @@ Ошибка доступа к Keychain при сохранении пароля No comment provided by engineer. + + Cannot receive file + Невозможно получить файл + No comment provided by engineer. + Change database passphrase? Поменять пароль базы данных? @@ -498,6 +508,11 @@ Превышено время соединения No comment provided by engineer. + + Contact %@ cannot be deleted, they are a member of the group(s) %@. + Контакт %@ не может быть удален, так как является членом групп(ы) %@. + No comment provided by engineer. + Contact already exists Существующий контакт @@ -911,6 +926,11 @@ Введите пароль… No comment provided by engineer. + + Error accepting contact request + Ошибка при принятии запроса на соединение + No comment provided by engineer. + Error accessing database file Ошибка при доступе к данным чата @@ -921,6 +941,11 @@ Ошибка при добавлении членов группы No comment provided by engineer. + + Error creating address + Ошибка при создании адреса + No comment provided by engineer. + Error creating group Ошибка при создании группы @@ -931,6 +956,11 @@ Ошибка при удалении данных чата No comment provided by engineer. + + Error deleting chat! + Ошибка при удалении чата! + No comment provided by engineer. + Error deleting database Ошибка при удалении данных чата @@ -971,6 +1001,11 @@ Ошибка приглашения No comment provided by engineer. + + Error receiving file + Ошибка при получении файла + No comment provided by engineer. + Error saving ICE servers Ошибка при сохранении ICE серверов @@ -986,6 +1021,11 @@ Ошибка при сохранении профиля группы No comment provided by engineer. + + Error sending message + Ошибка при отправке сообщения + No comment provided by engineer. + Error starting chat Ошибка при запуске чата @@ -996,6 +1036,11 @@ Ошибка при остановке чата No comment provided by engineer. + + Error updating message + Ошибка при обновлении сообщения + No comment provided by engineer. + Error updating settings Ошибка при сохранении настроек сети @@ -1928,6 +1973,16 @@ We will be adding server redundancy to prevent lost messages. Отправлять уведомления: No comment provided by engineer. + + Sender cancelled file transfer. + Отправитель отменил передачу файла. + No comment provided by engineer. + + + Sender may have deleted the connection request. + Отправитель мог удалить запрос на соединение. + No comment provided by engineer. + Sending via Отправка через diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 6d548e86f..de3311725 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -302,6 +302,7 @@ public enum ChatResponse: Decodable, Error { case groupUpdated(toGroup: GroupInfo) // receiving file events case rcvFileAccepted(chatItem: AChatItem) + case rcvFileAcceptedSndCancelled(rcvFileTransfer: RcvFileTransfer) case rcvFileStart(chatItem: AChatItem) case rcvFileComplete(chatItem: AChatItem) // sending file events @@ -392,6 +393,7 @@ public enum ChatResponse: Decodable, Error { case .groupRemoved: return "groupRemoved" case .groupUpdated: return "groupUpdated" case .rcvFileAccepted: return "rcvFileAccepted" + case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" case .rcvFileStart: return "rcvFileStart" case .rcvFileComplete: return "rcvFileComplete" case .sndFileStart: return "sndFileStart" @@ -484,6 +486,7 @@ public enum ChatResponse: Decodable, Error { case let .groupRemoved(groupInfo): return String(describing: groupInfo) case let .groupUpdated(toGroup): return String(describing: toGroup) case let .rcvFileAccepted(chatItem): return String(describing: chatItem) + case .rcvFileAcceptedSndCancelled: return noDetails case let .rcvFileStart(chatItem): return String(describing: chatItem) case let .rcvFileComplete(chatItem): return String(describing: chatItem) case let .sndFileStart(chatItem, _): return String(describing: chatItem) diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 181355ce0..60219eb96 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -221,6 +221,9 @@ /* No comment provided by engineer. */ "Calls" = "Звонки"; +/* No comment provided by engineer. */ +"Can't delete contact!" = "Невозможно удалить контакт!"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Нельзя пригласить контакт!"; @@ -233,6 +236,9 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Ошибка доступа к Keychain при сохранении пароля"; +/* No comment provided by engineer. */ +"Cannot receive file" = "Невозможно получить файл"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Поменять пароль базы данных?"; @@ -374,6 +380,9 @@ /* connection information */ "connection:%@" = "connection:%@"; +/* No comment provided by engineer. */ +"Contact %@ cannot be deleted, they are a member of the group(s) %@." = "Контакт %@ не может быть удален, так как является членом групп(ы) %@."; + /* No comment provided by engineer. */ "Contact already exists" = "Существующий контакт"; @@ -650,18 +659,27 @@ /* No comment provided by engineer. */ "error" = "ошибка"; +/* No comment provided by engineer. */ +"Error accepting contact request" = "Ошибка при принятии запроса на соединение"; + /* No comment provided by engineer. */ "Error accessing database file" = "Ошибка при доступе к данным чата"; /* No comment provided by engineer. */ "Error adding member(s)" = "Ошибка при добавлении членов группы"; +/* No comment provided by engineer. */ +"Error creating address" = "Ошибка при создании адреса"; + /* No comment provided by engineer. */ "Error creating group" = "Ошибка при создании группы"; /* No comment provided by engineer. */ "Error deleting chat database" = "Ошибка при удалении данных чата"; +/* No comment provided by engineer. */ +"Error deleting chat!" = "Ошибка при удалении чата!"; + /* No comment provided by engineer. */ "Error deleting database" = "Ошибка при удалении данных чата"; @@ -686,6 +704,9 @@ /* No comment provided by engineer. */ "Error joining group" = "Ошибка приглашения"; +/* No comment provided by engineer. */ +"Error receiving file" = "Ошибка при получении файла"; + /* No comment provided by engineer. */ "Error saving group profile" = "Ошибка при сохранении профиля группы"; @@ -695,12 +716,18 @@ /* No comment provided by engineer. */ "Error saving SMP servers" = "Ошибка при сохранении SMP серверов"; +/* No comment provided by engineer. */ +"Error sending message" = "Ошибка при отправке сообщения"; + /* No comment provided by engineer. */ "Error starting chat" = "Ошибка при запуске чата"; /* No comment provided by engineer. */ "Error stopping chat" = "Ошибка при остановке чата"; +/* No comment provided by engineer. */ +"Error updating message" = "Ошибка при обновлении сообщения"; + /* No comment provided by engineer. */ "Error updating settings" = "Ошибка при сохранении настроек сети"; @@ -1340,6 +1367,12 @@ /* No comment provided by engineer. */ "Send notifications:" = "Отправлять уведомления:"; +/* No comment provided by engineer. */ +"Sender cancelled file transfer." = "Отправитель отменил передачу файла."; + +/* No comment provided by engineer. */ +"Sender may have deleted the connection request." = "Отправитель мог удалить запрос на соединение."; + /* No comment provided by engineer. */ "Sending via" = "Отправка через"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 06ecffdd8..ae62ccec4 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -271,7 +271,6 @@ processChatCommand = \case setupSndFileTransfer :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd)) setupSndFileTransfer ct = forM file_ $ \file -> do (fileSize, chSize) <- checkSndFile file - -- [async agent commands] keep command synchronous, but process error (agentConnId, fileConnReq) <- withAgent $ \a -> createConnection a True SCMInvitation let fileName = takeFileName file fileInvitation = FileInvitation {fileName, fileSize, fileConnReq = Just fileConnReq} @@ -765,7 +764,6 @@ processChatCommand = \case case contactMember contact members of Nothing -> do gVar <- asks idsDrg - -- [async agent commands] keep command synchronous, but process error (agentConnId, cReq) <- withAgent $ \a -> createConnection a True SCMInvitation member <- withStore $ \db -> createNewContactMember db gVar user groupId contact memRole agentConnId cReq sendInvitation member cReq @@ -778,7 +776,6 @@ processChatCommand = \case APIJoinGroup groupId -> withUser $ \user@User {userId} -> do ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} <- withStore $ \db -> getGroupInvitation db user groupId withChatLock . procCmd $ do - -- [async agent commands] keep command synchronous, but process error agentConnId <- withAgent $ \a -> joinConnection a True connRequest . directMessage $ XGrpAcpt (memberId (membership :: GroupMember)) withStore' $ \db -> do createMemberConnection db userId fromMember agentConnId @@ -1131,7 +1128,6 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, fileInvitation = F case fileConnReq of -- direct file protocol Just connReq -> do - -- [async agent commands] keep command synchronous, but process error agentConnId <- withAgent $ \a -> joinConnection a True connReq . directMessage $ XFileAcpt fName filePath <- getRcvFilePath filePath_ fName withStore $ \db -> acceptRcvFileTransfer db user fileId agentConnId ConnJoined filePath