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:
@@ -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))")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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?"),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" = "Отправка через";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user