From ec7b35adb90f9a15ffb9ac9a62a660775d622821 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 20 May 2024 17:49:19 +0400 Subject: [PATCH] ios: check known relays before file reception, support user approval of unknown relays (#4044) * ios: check known relays before file reception, support user approval of unknown relays * add privacyAskToApproveRelays to AppSettings * less text * ui improvements, fixes * fix ntf * shorter alert * simplify settings * rename * icon --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/SimpleXAPI.swift | 54 +++++++++++++++---- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 2 +- .../Views/Chat/ChatItem/CIFileView.swift | 7 ++- .../Views/Chat/ChatItem/CIImageView.swift | 6 +-- .../Views/Chat/ChatItem/CIVideoView.swift | 17 ++++-- .../Views/Chat/ChatItem/CIVoiceView.swift | 7 +-- .../Views/Chat/ChatItem/FramedItemView.swift | 2 +- .../Views/UserSettings/AppSettings.swift | 2 + .../Views/UserSettings/PrivacySettings.swift | 39 +++++++++----- .../ios/SimpleX NSE/NotificationService.swift | 6 ++- apps/ios/SimpleXChat/APITypes.swift | 12 +++-- apps/ios/SimpleXChat/AppGroup.swift | 4 ++ apps/ios/SimpleXChat/ChatTypes.swift | 4 ++ 13 files changed, 121 insertions(+), 41 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ef31d2f438..a6d574e38d 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -927,14 +927,19 @@ func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationF } } -func receiveFile(user: any UserLike, fileId: Int64, auto: Bool = false) async { - if let chatItem = await apiReceiveFile(fileId: fileId, encrypted: privacyEncryptLocalFilesGroupDefault.get(), auto: auto) { +func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async { + if let chatItem = await apiReceiveFile( + fileId: fileId, + userApprovedRelays: userApprovedRelays || !privacyAskToApproveRelaysGroupDefault.get(), + encrypted: privacyEncryptLocalFilesGroupDefault.get(), + auto: auto + ) { await chatItemSimpleUpdate(user, chatItem) } } -func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? { - let r = await chatSendCmd(.receiveFile(fileId: fileId, encrypted: encrypted, inline: inline)) +func apiReceiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool, inline: Bool? = nil, auto: Bool = false) async -> AChatItem? { + let r = await chatSendCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) let am = AlertManager.shared if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } if case .rcvFileAcceptedSndCancelled = r { @@ -947,19 +952,50 @@ func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil, auto: B } } else if let networkErrorAlert = networkErrorAlert(r) { logger.error("apiReceiveFile network error: \(String(describing: r))") - am.showAlert(networkErrorAlert) + if !auto { + am.showAlert(networkErrorAlert) + } } else { switch chatError(r) { case .fileCancelled: logger.debug("apiReceiveFile ignoring fileCancelled error") case .fileAlreadyReceiving: logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error") + case let .fileNotApproved(fileId, unknownServers): + logger.debug("apiReceiveFile fileNotApproved error") + if !auto { + let srvs = unknownServers.map { s in + if let srv = parseServerAddress(s), !srv.hostnames.isEmpty { + srv.hostnames[0] + } else { + serverHost(s) + } + } + am.showAlert(Alert( + title: Text("Unknown servers!"), + message: Text("Without Tor or VPN, your IP address will be visible to these XFTP relays: \(srvs.sorted().joined(separator: ", "))."), + primaryButton: .default( + Text("Download"), + action: { + Task { + logger.debug("apiReceiveFile fileNotApproved alert - in Task") + if let user = ChatModel.shared.currentUser { + await receiveFile(user: user, fileId: fileId, userApprovedRelays: true) + } + } + } + ), + secondaryButton: .cancel() + )) + } default: logger.error("apiReceiveFile error: \(String(describing: r))") - am.showAlertMsg( - title: "Error receiving file", - message: "Error: \(String(describing: r))" - ) + if !auto { + am.showAlertMsg( + title: "Error receiving file", + message: "Error: \(String(describing: r))" + ) + } } } return nil diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 55e84f20d3..2e5a4f2af6 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -49,7 +49,7 @@ func localizedInfoRow(_ title: LocalizedStringKey, _ value: LocalizedStringKey) } } -private func serverHost(_ s: String) -> String { +func serverHost(_ s: String) -> String { if let i = s.range(of: "@")?.lowerBound { return String(s[i...].dropFirst()) } else { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index ae9e09b138..0af0469e42 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -60,6 +60,7 @@ struct CIFileView: View { case .rcvInvitation: return true case .rcvAccepted: return true case .rcvTransfer: return false + case .rcvAborted: return true case .rcvComplete: return true case .rcvCancelled: return false case .rcvError: return false @@ -73,10 +74,10 @@ struct CIFileView: View { logger.debug("CIFileView fileAction") if let file = file { switch (file.fileStatus) { - case .rcvInvitation: + case .rcvInvitation, .rcvAborted: if fileSizeValid(file) { Task { - logger.debug("CIFileView fileAction - in .rcvInvitation, in Task") + logger.debug("CIFileView fileAction - in .rcvInvitation, .rcvAborted, in Task") if let user = m.currentUser { await receiveFile(user: user, fileId: file.fileId) } @@ -148,6 +149,8 @@ struct CIFileView: View { } else { progressView() } + case .rcvAborted: + fileIcon("doc.fill", color: .accentColor, innerIcon: "exclamationmark.arrow.circlepath", innerIconSize: 12) case .rcvComplete: fileIcon("doc.fill") case .rcvCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10) case .rcvError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index 16974147c8..7d33df6c60 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -17,7 +17,6 @@ struct CIImageView: View { let maxWidth: CGFloat @Binding var imgWidth: CGFloat? @State var scrollProxy: ScrollViewProxy? - @State var metaColor: Color @State private var showFullScreenImage = false var body: some View { @@ -38,7 +37,7 @@ struct CIImageView: View { .onTapGesture { if let file = file { switch file.fileStatus { - case .rcvInvitation: + case .rcvInvitation, .rcvAborted: Task { if let user = m.currentUser { await receiveFile(user: user, fileId: file.fileId) @@ -103,6 +102,7 @@ struct CIImageView: View { case .rcvInvitation: fileIcon("arrow.down", 10, 13) case .rcvAccepted: fileIcon("ellipsis", 14, 11) case .rcvTransfer: progressView() + case .rcvAborted: fileIcon("exclamationmark.arrow.circlepath", 14, 11) case .rcvCancelled: fileIcon("xmark", 10, 13) case .rcvError: fileIcon("xmark", 10, 13) case .invalid: fileIcon("questionmark", 10, 13) @@ -116,7 +116,7 @@ struct CIImageView: View { .resizable() .aspectRatio(contentMode: .fit) .frame(width: size, height: size) - .foregroundColor(metaColor) + .foregroundColor(.white) .padding(padding) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index b4b190a43a..3bfe24b79d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -69,7 +69,7 @@ struct CIVideoView: View { .onTapGesture { if let file = file { switch file.fileStatus { - case .rcvInvitation: + case .rcvInvitation, .rcvAborted: receiveFileIfValidSize(file: file, receiveFile: receiveFile) case .rcvAccepted: switch file.fileProtocol { @@ -95,7 +95,7 @@ struct CIVideoView: View { } durationProgress() } - if let file = file, case .rcvInvitation = file.fileStatus { + if let file = file, showDownloadButton(file.fileStatus) { Button { receiveFileIfValidSize(file: file, receiveFile: receiveFile) } label: { @@ -105,6 +105,14 @@ struct CIVideoView: View { } } + private func showDownloadButton(_ fileStatus: CIFileStatus) -> Bool { + switch fileStatus { + case .rcvInvitation: true + case .rcvAborted: true + default: false + } + } + private func videoViewEncrypted(_ file: CIFile, _ defaultPreview: UIImage, _ duration: Int) -> some View { return ZStack(alignment: .topTrailing) { ZStack(alignment: .center) { @@ -280,6 +288,7 @@ struct CIVideoView: View { } else { progressView() } + case .rcvAborted: fileIcon("exclamationmark.arrow.circlepath", 14, 11) case .rcvCancelled: fileIcon("xmark", 10, 13) case .rcvError: fileIcon("xmark", 10, 13) case .invalid: fileIcon("questionmark", 10, 13) @@ -318,10 +327,10 @@ struct CIVideoView: View { } // TODO encrypt: where file size is checked? - private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64, Bool) async -> Void) { + private func receiveFileIfValidSize(file: CIFile, receiveFile: @escaping (User, Int64, Bool, Bool) async -> Void) { Task { if let user = m.currentUser { - await receiveFile(user, file.fileId, false) + await receiveFile(user, file.fileId, false, false) } } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index 3aecb65ebd..ba1712f6e3 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -139,9 +139,10 @@ struct VoiceMessagePlayer: View { case .sndComplete: playbackButton() case .sndCancelled: playbackButton() case .sndError: playbackButton() - case .rcvInvitation: downloadButton(recordingFile) + case .rcvInvitation: downloadButton(recordingFile, "play.fill") case .rcvAccepted: loadingIcon() case .rcvTransfer: loadingIcon() + case .rcvAborted: downloadButton(recordingFile, "exclamationmark.arrow.circlepath") case .rcvComplete: playbackButton() case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) case .rcvError: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) @@ -217,7 +218,7 @@ struct VoiceMessagePlayer: View { } } - private func downloadButton(_ recordingFile: CIFile) -> some View { + private func downloadButton(_ recordingFile: CIFile, _ icon: String) -> some View { Button { Task { if let user = chatModel.currentUser { @@ -225,7 +226,7 @@ struct VoiceMessagePlayer: View { } } } label: { - playPauseIcon("play.fill") + playPauseIcon(icon) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index a1642769b3..9b4cecf526 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -115,7 +115,7 @@ struct FramedItemView: View { } else { switch (chatItem.content.msgContent) { case let .image(text, image): - CIImageView(chatItem: chatItem, image: image, maxWidth: maxWidth, imgWidth: $imgWidth, scrollProxy: scrollProxy, metaColor: metaColor) + CIImageView(chatItem: chatItem, image: image, maxWidth: maxWidth, imgWidth: $imgWidth, scrollProxy: scrollProxy) .overlay(DetermineWidth()) if text == "" && !chatItem.meta.isLive { Color.clear diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index ba192b333c..299c96626a 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -23,6 +23,7 @@ extension AppSettings { setNetCfg(val) } if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) } + if let val = privacyAskToApproveRelays { privacyAskToApproveRelaysGroupDefault.set(val) } if let val = privacyAcceptImages { privacyAcceptImagesGroupDefault.set(val) def.setValue(val, forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES) @@ -50,6 +51,7 @@ extension AppSettings { var c = AppSettings.defaults c.networkConfig = getNetCfg() c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get() + c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get() c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() c.privacyLinkPreviews = def.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) c.privacyShowChatPreviews = def.bool(forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 8d13c6fb39..01f31d66b4 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -16,6 +16,7 @@ struct PrivacySettings: View { @AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true @AppStorage(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true @AppStorage(GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES, store: groupDefaults) private var encryptLocalFiles = true + @AppStorage(GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS, store: groupDefaults) private var askToApproveRelays = true @State private var simplexLinkMode = privacySimplexLinkModeDefault.get() @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @@ -64,18 +65,6 @@ struct PrivacySettings: View { } Section { - settingsRow("lock.doc") { - Toggle("Encrypt local files", isOn: $encryptLocalFiles) - .onChange(of: encryptLocalFiles) { - setEncryptLocalFiles($0) - } - } - settingsRow("photo") { - Toggle("Auto-accept images", isOn: $autoAcceptImages) - .onChange(of: autoAcceptImages) { - privacyAcceptImagesGroupDefault.set($0) - } - } settingsRow("network") { Toggle("Send link previews", isOn: $useLinkPreviews) } @@ -108,6 +97,32 @@ struct PrivacySettings: View { Text("Chats") } + Section { + settingsRow("lock.doc") { + Toggle("Encrypt local files", isOn: $encryptLocalFiles) + .onChange(of: encryptLocalFiles) { + setEncryptLocalFiles($0) + } + } + settingsRow("photo") { + Toggle("Auto-accept images", isOn: $autoAcceptImages) + .onChange(of: autoAcceptImages) { + privacyAcceptImagesGroupDefault.set($0) + } + } + settingsRow("network.badge.shield.half.filled") { + Toggle("Protect IP address", isOn: $askToApproveRelays) + } + } header: { + Text("Files") + } footer: { + if askToApproveRelays { + Text("The app will ask to confirm downloads from unknown file servers (except .onion).") + } else { + Text("Without Tor or VPN, your IP address will be visible to file servers.") + } + } + Section { settingsRow("person") { Toggle("Contacts", isOn: $contactReceipts) diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index faa7f4f44c..12094c7053 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -696,14 +696,16 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { } func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? { - let r = sendSimpleXCmd(.receiveFile(fileId: fileId, encrypted: encrypted, inline: inline)) + let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() + let r = sendSimpleXCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline)) if case let .rcvFileAccepted(_, chatItem) = r { return chatItem } logger.error("receiveFile error: \(responseError(r))") return nil } func apiSetFileToReceive(fileId: Int64, encrypted: Bool) { - let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId, encrypted: encrypted)) + let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get() + let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted)) if case .cmdOk = r { return } logger.error("setFileToReceive error: \(responseError(r))") } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 97013ca2a4..7fa7e961ae 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -123,8 +123,8 @@ public enum ChatCommand { case apiGetNetworkStatuses case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) - case receiveFile(fileId: Int64, encrypted: Bool?, inline: Bool?) - case setFileToReceive(fileId: Int64, encrypted: Bool?) + case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?) + case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?) case cancelFile(fileId: Int64) // remote desktop commands case setLocalDeviceName(displayName: String) @@ -282,8 +282,8 @@ public enum ChatCommand { case .apiGetNetworkStatuses: return "/_network_statuses" case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" - case let .receiveFile(fileId, encrypt, inline): return "/freceive \(fileId)\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" - case let .setFileToReceive(fileId, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("encrypt", encrypt))" + case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))" + case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))" case let .cancelFile(fileId): return "/fcancel \(fileId)" case let .setLocalDeviceName(displayName): return "/set device name \(displayName)" case let .connectRemoteCtrl(xrcpInv): return "/connect remote ctrl \(xrcpInv)" @@ -1760,6 +1760,7 @@ public enum ChatErrorType: Decodable { case fileImageType(filePath: String) case fileImageSize(filePath: String) case fileNotReceived(fileId: Int64) + case fileNotApproved(fileId: Int64, unknownServers: [String]) // case xFTPRcvFile // case xFTPSndFile case fallbackToSMPProhibited(fileId: Int64) @@ -2038,6 +2039,7 @@ public struct MigrationFileLinkData: Codable { public struct AppSettings: Codable, Equatable { public var networkConfig: NetCfg? = nil public var privacyEncryptLocalFiles: Bool? = nil + public var privacyAskToApproveRelays: Bool? = nil public var privacyAcceptImages: Bool? = nil public var privacyLinkPreviews: Bool? = nil public var privacyShowChatPreviews: Bool? = nil @@ -2061,6 +2063,7 @@ public struct AppSettings: Codable, Equatable { let def = AppSettings.defaults if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } + if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays } if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } @@ -2085,6 +2088,7 @@ public struct AppSettings: Codable, Equatable { AppSettings ( networkConfig: NetCfg.defaults, privacyEncryptLocalFiles: true, + privacyAskToApproveRelays: true, privacyAcceptImages: true, privacyLinkPreviews: true, privacyShowChatPreviews: true, diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index 118acae993..90ac403999 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -23,6 +23,7 @@ public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic" // no longer let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used public let GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES = "privacyEncryptLocalFiles" +public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRelays" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode" @@ -73,6 +74,7 @@ public func registerGroupDefaults() { GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES: true, GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE: false, GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true, + GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS: true, GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false, GROUP_DEFAULT_CALL_KIT_ENABLED: true, GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false, @@ -181,6 +183,8 @@ public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults public let privacyEncryptLocalFilesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES) +public let privacyAskToApproveRelaysGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS) + public let ntfBadgeCountGroupDefault = IntDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_BADGE_COUNT) public let networkUseOnionHostsGroupDefault = EnumDefault( diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 0d2a042d9d..27e5a9818c 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -3174,6 +3174,7 @@ public struct CIFile: Decodable { case .rcvInvitation: return false case .rcvAccepted: return false case .rcvTransfer: return false + case .rcvAborted: return false case .rcvCancelled: return false case .rcvComplete: return true case .rcvError: return false @@ -3198,6 +3199,7 @@ public struct CIFile: Decodable { case .rcvInvitation: return nil case .rcvAccepted: return rcvCancelAction case .rcvTransfer: return rcvCancelAction + case .rcvAborted: return nil case .rcvCancelled: return nil case .rcvComplete: return nil case .rcvError: return nil @@ -3312,6 +3314,7 @@ public enum CIFileStatus: Decodable, Equatable { case rcvInvitation case rcvAccepted case rcvTransfer(rcvProgress: Int64, rcvTotal: Int64) + case rcvAborted case rcvComplete case rcvCancelled case rcvError @@ -3327,6 +3330,7 @@ public enum CIFileStatus: Decodable, Equatable { case .rcvInvitation: return "rcvInvitation" case .rcvAccepted: return "rcvAccepted" case let .rcvTransfer(rcvProgress, rcvTotal): return "rcvTransfer \(rcvProgress) \(rcvTotal)" + case .rcvAborted: return "rcvAborted" case .rcvComplete: return "rcvComplete" case .rcvCancelled: return "rcvCancelled" case .rcvError: return "rcvError"