ios: alerts on connection errors (#1080)

* ios: alerts on connection errors

* fixes

* refactor

* refactor

* refactor

* translations

* delete contact with groups error

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
JRoberts
2022-09-21 17:18:48 +04:00
committed by GitHub
parent 0494cce77d
commit 9442656bbe
13 changed files with 381 additions and 135 deletions

View File

@@ -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))")
}
}

View File

@@ -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?"),

View File

@@ -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()
}
}

View File

@@ -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))
}
}
}
}

View File

@@ -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 {

View File

@@ -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!",

View File

@@ -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)")
}
}

View File

@@ -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)"))
}
}
}
}
}

View File

@@ -308,6 +308,11 @@
<target>Calls</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Can't delete contact!" xml:space="preserve">
<source>Can't delete contact!</source>
<target>Can't delete contact!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Can't invite contact!" xml:space="preserve">
<source>Can't invite contact!</source>
<target>Can't invite contact!</target>
@@ -328,6 +333,11 @@
<target>Cannot access keychain to save database password</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot receive file" xml:space="preserve">
<source>Cannot receive file</source>
<target>Cannot receive file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Change database passphrase?" xml:space="preserve">
<source>Change database passphrase?</source>
<target>Change database passphrase?</target>
@@ -498,6 +508,11 @@
<target>Connection timeout</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact %@ cannot be deleted, they are a member of the group(s) %@." xml:space="preserve">
<source>Contact %@ cannot be deleted, they are a member of the group(s) %@.</source>
<target>Contact %@ cannot be deleted, they are a member of the group(s) %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact already exists" xml:space="preserve">
<source>Contact already exists</source>
<target>Contact already exists</target>
@@ -911,6 +926,11 @@
<target>Enter passphrase…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting contact request" xml:space="preserve">
<source>Error accepting contact request</source>
<target>Error accepting contact request</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accessing database file" xml:space="preserve">
<source>Error accessing database file</source>
<target>Error accessing database file</target>
@@ -921,6 +941,11 @@
<target>Error adding member(s)</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Error creating address</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating group" xml:space="preserve">
<source>Error creating group</source>
<target>Error creating group</target>
@@ -931,6 +956,11 @@
<target>Error deleting chat database</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Error deleting chat!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting database" xml:space="preserve">
<source>Error deleting database</source>
<target>Error deleting database</target>
@@ -971,6 +1001,11 @@
<target>Error joining group</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error receiving file" xml:space="preserve">
<source>Error receiving file</source>
<target>Error receiving file</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error saving ICE servers" xml:space="preserve">
<source>Error saving ICE servers</source>
<target>Error saving ICE servers</target>
@@ -986,6 +1021,11 @@
<target>Error saving group profile</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Error sending message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error starting chat" xml:space="preserve">
<source>Error starting chat</source>
<target>Error starting chat</target>
@@ -996,6 +1036,11 @@
<target>Error stopping chat</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error updating message" xml:space="preserve">
<source>Error updating message</source>
<target>Error updating message</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error updating settings" xml:space="preserve">
<source>Error updating settings</source>
<target>Error updating settings</target>
@@ -1928,6 +1973,16 @@ We will be adding server redundancy to prevent lost messages.</target>
<target>Send notifications:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
<source>Sender cancelled file transfer.</source>
<target>Sender cancelled file transfer.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sender may have deleted the connection request." xml:space="preserve">
<source>Sender may have deleted the connection request.</source>
<target>Sender may have deleted the connection request.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending via" xml:space="preserve">
<source>Sending via</source>
<target>Sending via</target>

View File

@@ -308,6 +308,11 @@
<target>Звонки</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Can't delete contact!" xml:space="preserve">
<source>Can't delete contact!</source>
<target>Невозможно удалить контакт!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Can't invite contact!" xml:space="preserve">
<source>Can't invite contact!</source>
<target>Нельзя пригласить контакт!</target>
@@ -328,6 +333,11 @@
<target>Ошибка доступа к Keychain при сохранении пароля</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Cannot receive file" xml:space="preserve">
<source>Cannot receive file</source>
<target>Невозможно получить файл</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Change database passphrase?" xml:space="preserve">
<source>Change database passphrase?</source>
<target>Поменять пароль базы данных?</target>
@@ -498,6 +508,11 @@
<target>Превышено время соединения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact %@ cannot be deleted, they are a member of the group(s) %@." xml:space="preserve">
<source>Contact %@ cannot be deleted, they are a member of the group(s) %@.</source>
<target>Контакт %@ не может быть удален, так как является членом групп(ы) %@.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Contact already exists" xml:space="preserve">
<source>Contact already exists</source>
<target>Существующий контакт</target>
@@ -911,6 +926,11 @@
<target>Введите пароль…</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accepting contact request" xml:space="preserve">
<source>Error accepting contact request</source>
<target>Ошибка при принятии запроса на соединение</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error accessing database file" xml:space="preserve">
<source>Error accessing database file</source>
<target>Ошибка при доступе к данным чата</target>
@@ -921,6 +941,11 @@
<target>Ошибка при добавлении членов группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating address" xml:space="preserve">
<source>Error creating address</source>
<target>Ошибка при создании адреса</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error creating group" xml:space="preserve">
<source>Error creating group</source>
<target>Ошибка при создании группы</target>
@@ -931,6 +956,11 @@
<target>Ошибка при удалении данных чата</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting chat!" xml:space="preserve">
<source>Error deleting chat!</source>
<target>Ошибка при удалении чата!</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error deleting database" xml:space="preserve">
<source>Error deleting database</source>
<target>Ошибка при удалении данных чата</target>
@@ -971,6 +1001,11 @@
<target>Ошибка приглашения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error receiving file" xml:space="preserve">
<source>Error receiving file</source>
<target>Ошибка при получении файла</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error saving ICE servers" xml:space="preserve">
<source>Error saving ICE servers</source>
<target>Ошибка при сохранении ICE серверов</target>
@@ -986,6 +1021,11 @@
<target>Ошибка при сохранении профиля группы</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error sending message" xml:space="preserve">
<source>Error sending message</source>
<target>Ошибка при отправке сообщения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error starting chat" xml:space="preserve">
<source>Error starting chat</source>
<target>Ошибка при запуске чата</target>
@@ -996,6 +1036,11 @@
<target>Ошибка при остановке чата</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error updating message" xml:space="preserve">
<source>Error updating message</source>
<target>Ошибка при обновлении сообщения</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Error updating settings" xml:space="preserve">
<source>Error updating settings</source>
<target>Ошибка при сохранении настроек сети</target>
@@ -1928,6 +1973,16 @@ We will be adding server redundancy to prevent lost messages.</source>
<target>Отправлять уведомления:</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sender cancelled file transfer." xml:space="preserve">
<source>Sender cancelled file transfer.</source>
<target>Отправитель отменил передачу файла.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sender may have deleted the connection request." xml:space="preserve">
<source>Sender may have deleted the connection request.</source>
<target>Отправитель мог удалить запрос на соединение.</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="Sending via" xml:space="preserve">
<source>Sending via</source>
<target>Отправка через</target>

View File

@@ -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)

View File

@@ -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" = "Отправка через";

View File

@@ -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