diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 657a6f9b67..5a4fe4a667 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -12,8 +12,10 @@ 2C1D5D7923E2DE9100334ABB /* NCBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76B3CCD1EAE01BD00921AC9 /* NCBrand.swift */; }; 2C33C48223E2C475005F963B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33C48123E2C475005F963B /* NotificationService.swift */; }; 2C33C48623E2C475005F963B /* Notification Service Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2C33C47F23E2C475005F963B /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 2EF7D3FD0885CE182717578E /* NKError+UserFriendlyMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79DFE0BDF3898A66910E67CA /* NKError+UserFriendlyMessage.swift */; }; 370D26AF248A3D7A00121797 /* NCCellMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370D26AE248A3D7A00121797 /* NCCellMain.swift */; }; 63563FE53573412998852DD3 /* NCAlbumsPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439616AAA50F4360A761E5EF /* NCAlbumsPersistenceTests.swift */; }; + 822D68B06B101563A759AD59 /* NKError+UserFriendlyMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79DFE0BDF3898A66910E67CA /* NKError+UserFriendlyMessage.swift */; }; A12720261001000000000001 /* NCBackgroundUploadExtensionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F793E7C230417C7100F011FE /* NCBackgroundUploadExtensionManager.swift */; }; A1E2EE010000000000000002 /* NCEndToEndKeySet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E2EE010000000000000001 /* NCEndToEndKeySet.swift */; }; A1E2EE010000000000000003 /* NCEndToEndKeySet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E2EE010000000000000001 /* NCEndToEndKeySet.swift */; }; @@ -28,6 +30,7 @@ A1E2EE020000000000000005 /* NCEndToEndKeySetResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E2EE020000000000000002 /* NCEndToEndKeySetResolver.swift */; }; A1E2EE020000000000000006 /* NCEndToEndKeySetResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E2EE020000000000000002 /* NCEndToEndKeySetResolver.swift */; }; A1E2EE020000000000000008 /* NCEndToEndKeySetResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E2EE020000000000000007 /* NCEndToEndKeySetResolverTests.swift */; }; + A358B569BF5FDABA6D9012D1 /* NCErrorContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0CD9A00D74B01055F238E9 /* NCErrorContext.swift */; }; A5A87F9E4B0E4441A6A4BC20 /* NCContextMenuProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7697C94BA14450A0867940 /* NCContextMenuProfile.swift */; }; AA3C85E82D36B08C00F74F12 /* UITestBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3C85E72D36B08C00F74F12 /* UITestBackend.swift */; }; AA3C85EB2D36BBFB00F74F12 /* OCSResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3C85EA2D36BBF400F74F12 /* OCSResponse.swift */; }; @@ -112,6 +115,7 @@ AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; B601AA012F10000100C0D3E5 /* NCLogFilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B601AA002F10000100C0D3E5 /* NCLogFilesView.swift */; }; + BAF64949B487A080A98F83F2 /* NKErrorUserFriendlyMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320C20803A58DAF7575440A8 /* NKErrorUserFriendlyMessageTests.swift */; }; C0DEA070306D000100000005 /* NCLocalDatabase+AutoUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEA070306D000100000004 /* NCLocalDatabase+AutoUpload.swift */; }; C0DEA070306D000100000006 /* NCLocalDatabase+AutoUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEA070306D000100000004 /* NCLocalDatabase+AutoUpload.swift */; }; C0DEA070306D000100000008 /* NCAutoUploadAssetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEA070306D000100000007 /* NCAutoUploadAssetTests.swift */; }; @@ -119,6 +123,7 @@ C0DEDA7E30C1000100A00002 /* NCAutoUploadDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DEDA7E30C1000100A00001 /* NCAutoUploadDateTests.swift */; }; CB3666201AF7550816B5CD6A /* NCContextMenuComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; + EB511D22564B7B9A605E70AE /* NCErrorContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE0CD9A00D74B01055F238E9 /* NCErrorContext.swift */; }; F0A1B2C430B5000100D4E5F6 /* NCMediaViewerFloatingTitleViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A1B2C330B5000100D4E5F6 /* NCMediaViewerFloatingTitleViewTests.swift */; }; F0A1B2C630B6000100D4E5F6 /* NCImageZoomViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A1B2C530B6000100D4E5F6 /* NCImageZoomViewTests.swift */; }; F31165022F9674A1009A1E37 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = F31165012F9674A1009A1E37 /* AppIcon.icon */; }; @@ -1334,9 +1339,11 @@ 2C33C47F23E2C475005F963B /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 2C33C48123E2C475005F963B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 2C33C48A23E2CC26005F963B /* Notification_Service_Extension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Notification_Service_Extension-Bridging-Header.h"; sourceTree = ""; }; + 320C20803A58DAF7575440A8 /* NKErrorUserFriendlyMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NKErrorUserFriendlyMessageTests.swift; sourceTree = ""; }; 370D26AE248A3D7A00121797 /* NCCellMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCellMain.swift; sourceTree = ""; }; 439616AAA50F4360A761E5EF /* NCAlbumsPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAlbumsPersistenceTests.swift; sourceTree = ""; }; 4B74B947E07444CC9CFE0A6C /* NCDocumentEditorSupportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCDocumentEditorSupportTests.swift; sourceTree = ""; }; + 79DFE0BDF3898A66910E67CA /* NKError+UserFriendlyMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NKError+UserFriendlyMessage.swift"; sourceTree = ""; }; 8932E90EC4278026D86CCCC9 /* NCContextMenuComment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenuComment.swift; sourceTree = ""; }; A1E2EE010000000000000001 /* NCEndToEndKeySet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCEndToEndKeySet.swift; sourceTree = ""; }; A1E2EE010000000000000009 /* NCEndToEndKeySetArchiveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCEndToEndKeySetArchiveTests.swift; sourceTree = ""; }; @@ -1480,6 +1487,7 @@ C0DEA070306D000100000007 /* NCAutoUploadAssetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAutoUploadAssetTests.swift; sourceTree = ""; }; C0DECA012F65000100C0D001 /* NCCameraRollTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCameraRollTests.swift; sourceTree = ""; }; C0DEDA7E30C1000100A00001 /* NCAutoUploadDateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAutoUploadDateTests.swift; sourceTree = ""; }; + CE0CD9A00D74B01055F238E9 /* NCErrorContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCErrorContext.swift; sourceTree = ""; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; F0A1B2C330B5000100D4E5F6 /* NCMediaViewerFloatingTitleViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaViewerFloatingTitleViewTests.swift; sourceTree = ""; }; F0A1B2C530B6000100D4E5F6 /* NCImageZoomViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCImageZoomViewTests.swift; sourceTree = ""; }; @@ -2388,6 +2396,7 @@ children = ( AC13ADAF2026101300000001 /* NCMediaAdaptiveLayoutViewTests.swift */, C0DEDA7E30C1000100A00001 /* NCAutoUploadDateTests.swift */, + 320C20803A58DAF7575440A8 /* NKErrorUserFriendlyMessageTests.swift */, C0DEA070306D000100000007 /* NCAutoUploadAssetTests.swift */, F76BED7B306D0000002E1442 /* NCPhotoLibraryAssetTests.swift */, A1E2EE010000000000000009 /* NCEndToEndKeySetArchiveTests.swift */, @@ -2814,6 +2823,8 @@ F785129A2D79899E0087DDD0 /* NCNetworking+TermsOfService.swift */, F71916102E2901E800E13E96 /* NCNetworking+Upload.swift */, F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */, + 79DFE0BDF3898A66910E67CA /* NKError+UserFriendlyMessage.swift */, + CE0CD9A00D74B01055F238E9 /* NCErrorContext.swift */, F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */, F755BD9A20594AC7008C5FBB /* NCService.swift */, ); @@ -4851,6 +4862,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BAF64949B487A080A98F83F2 /* NKErrorUserFriendlyMessageTests.swift in Sources */, AC13ADAF2026101300000002 /* NCMediaAdaptiveLayoutViewTests.swift in Sources */, C0DEDA7E30C1000100A00002 /* NCAutoUploadDateTests.swift in Sources */, C0DEA070306D000100000008 /* NCAutoUploadAssetTests.swift in Sources */, @@ -4944,6 +4956,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2EF7D3FD0885CE182717578E /* NKError+UserFriendlyMessage.swift in Sources */, + EB511D22564B7B9A605E70AE /* NCErrorContext.swift in Sources */, F7864ACF2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */, F71F6D0A2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */, F7245925289BB59100474787 /* ThreadSafeDictionary.swift in Sources */, @@ -5206,6 +5220,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 822D68B06B101563A759AD59 /* NKError+UserFriendlyMessage.swift in Sources */, + A358B569BF5FDABA6D9012D1 /* NCErrorContext.swift in Sources */, F76BED77306D0000002E1442 /* NCLocalDatabase.swift in Sources */, F76BED78306D0000002E1442 /* NCLocalDatabaseMigrator.swift in Sources */, F76BED7C306D0000002E1442 /* NCLocalDatabase+PhotoLibraryAsset.swift in Sources */, @@ -7279,8 +7295,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - kind = exactVersion; - version = 7.6.2; + branch = "friendly-errors"; + kind = branch; }; }; F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = { diff --git a/Nextcloud.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Nextcloud.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 87db5d5f5e..db676d9f57 100644 --- a/Nextcloud.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Nextcloud.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -204,8 +204,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/nextcloud/NextcloudKit", "state" : { - "revision" : "705e69cf0526cac0bdeaf21699c41c895cae42a0", - "version" : "7.6.2" + "branch" : "friendly-errors", + "revision" : "4ac49ab5fa4e5f3b112afff7658713d52dbaf593" } }, { diff --git a/Tests/NextcloudUnitTests/NKErrorUserFriendlyMessageTests.swift b/Tests/NextcloudUnitTests/NKErrorUserFriendlyMessageTests.swift new file mode 100644 index 0000000000..bb77c5350a --- /dev/null +++ b/Tests/NextcloudUnitTests/NKErrorUserFriendlyMessageTests.swift @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import NextcloudKit +import Testing +@testable import Nextcloud + +@Suite("Error messages shown to people") +struct NKErrorUserFriendlyMessageTests { + static let shownKinds = NKError.Kind.allCases.filter { $0 != .cancelled } + + private func localized(_ key: String) -> String { + NSLocalizedString(key, comment: "") + } + + @Test("Nothing is shown for success or cancellation") + func silentErrors() { + #expect(NKError.success.userFriendlyMessage() == nil) + #expect(NKError.cancelled.userFriendlyMessage(for: .upload(fileName: "a.jpg")) == nil) + #expect(NKError(errorCode: NCGlobal.shared.errorTaskCancelled, errorDescription: "Task cancelled").userFriendlyMessage() == nil) + } + + @Test("Keeps text the app wrote itself") + func appMessages() { + #expect(NKError(errorCode: NCGlobal.shared.errorOfflineNotAllowed, errorDescription: "_offline_not_allowed_").userFriendlyMessage() == localized("_offline_not_allowed_")) + #expect(NKError(errorCode: NCGlobal.shared.errorE2EELock, errorDescription: "Already translated").userFriendlyMessage() == "Already translated") + } + + @Test("Replaces technical server text") + func technicalServerText() throws { + let error = NKError(errorCode: 409, errorDescription: "Parent node does not exist") + let generic = try #require(error.userFriendlyMessage()) + + #expect(error.userFriendlyMessage(for: .createFolder(folderName: "a/b")) == localized("_error_create_folder_no_parent_")) + #expect(generic != error.errorDescription) + #expect(!generic.contains("409")) + } + + @Test("Uses the generic message unless the operation needs its own") + func operationMessages() { + let forbidden = NKError(errorCode: 403, errorDescription: "403: Forbidden") + let generic = forbidden.userFriendlyMessage() + + #expect(generic != nil) + #expect(forbidden.userFriendlyMessage(for: .delete(fileName: "a.jpg")) == generic) + #expect(forbidden.userFriendlyMessage(for: .rename) == generic) + #expect(forbidden.userFriendlyMessage(for: .upload(fileName: "a.jpg")) == generic) + } + + @Test("Recognizes uploads rejected by the virus scanner") + func virusDetected() { + let error = NKError(errorCode: 415, errorDescription: "Virus Eicar-Signature is detected in the file. Upload cannot be completed.") + + #expect(error.userFriendlyMessage(for: .upload(fileName: "eicar.com")) == localized("_virus_detect_")) + } + + @Test("Words different kinds of failure differently") + func kindMessages() { + let timeout = NKError(errorCode: NSURLErrorTimedOut, errorDescription: "The request timed out.").userFriendlyMessage() + let quota = NKError(errorCode: 507, errorDescription: "507: Storage quota is reached").userFriendlyMessage() + let unknown = NKError(errorCode: 418, errorDescription: "418: I'm a teapot").userFriendlyMessage() + + #expect(Set([timeout, quota, unknown]).count == 3) + #expect(quota != "507: Storage quota is reached") + } + + @Test("Every kind has a translated message", arguments: NKErrorUserFriendlyMessageTests.shownKinds) + func everyKind(kind: NKError.Kind) { + let message = NKError(errorCode: -1, errorDescription: "Technical", kind: kind).userFriendlyMessage() + + #expect(message != nil) + #expect(message != "Technical") + } + + @Test("Maps transfer events and saved statuses to operations") + func contexts() { + let global = NCGlobal.shared + + #expect(NCErrorContext(networkingStatus: global.networkingStatusUploaded, fileName: "a.jpg") == .upload(fileName: "a.jpg")) + #expect(NCErrorContext(networkingStatus: global.networkingStatusCopyMove, fileName: "a.jpg") == .moveOrCopy(fileName: "a.jpg")) + #expect(NCErrorContext(networkingStatus: global.networkingStatusReloadAvatar, fileName: "") == .generic) + #expect(NCErrorContext(metadataStatus: global.metadataStatusWaitCreateFolder, fileName: "Photos") == .createFolder(folderName: "Photos")) + #expect(NCErrorContext(metadataStatus: global.metadataStatusDownloadError, fileName: "a.jpg") == .download(fileName: "a.jpg")) + } + + @Test("Titles say what failed") + func failureTitles() throws { + let contexts: [NCErrorContext] = [.upload(fileName: "f"), .download(fileName: "f"), .delete(fileName: "f"), .rename, .moveOrCopy(fileName: "f"), .createFolder(folderName: "f"), .favorite(fileName: "f")] + + #expect(NCErrorContext.generic.failureTitle == nil) + #expect(NCErrorContext.upload(fileName: "IMG_0042.jpg").failureTitle?.contains("IMG_0042.jpg") == true) + + for context in contexts { + let title = try #require(context.failureTitle) + + #expect(!title.hasPrefix("_")) + } + } + + @Test("Words saved transfer errors") + func savedErrors() { + let metadata = tableMetadata() + metadata.fileNameView = "a.jpg" + metadata.status = NCGlobal.shared.metadataStatusUploadError + metadata.errorCode = 507 + metadata.sessionError = "507: Storage quota is reached" + + #expect(metadata.sessionErrorMessage == NKError(errorCode: 507, errorDescription: "").userFriendlyMessage()) + + metadata.errorCode = NSURLErrorCancelled + metadata.sessionError = "Transfer was cancelled." + + #expect(metadata.sessionErrorMessage != "Transfer was cancelled.") + #expect(!metadata.sessionErrorMessage.isEmpty) + } +} diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 9290195d07..dfb5aa76b1 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -86,8 +86,7 @@ class NCActivity: UIViewController, NCSharePagingContent { } else { Task { await showErrorBanner(windowScene: self.windowScene, - text: error.errorDescription, - errorCode: error.errorCode) + error: error) } } } @@ -447,8 +446,7 @@ extension NCActivity { } else if error.errorCode != NCGlobal.shared.errorResourceNotFound { Task { await showErrorBanner(windowScene: self.windowScene, - text: error.errorDescription, - errorCode: error.errorCode) + error: error) } } diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 25aa8f8659..44fd79acb7 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -378,6 +378,18 @@ extension tableMetadata { } } +#if !EXTENSION || EXTENSION_SHARE +extension tableMetadata { + /// The saved `sessionError`, worded for people from its `errorCode` and the operation the item failed at. + var sessionErrorMessage: String { + let error = NKError(errorCode: errorCode, errorDescription: sessionError) + let context = NCErrorContext(metadataStatus: status, fileName: fileNameView) + + return error.userFriendlyMessage(for: context) ?? error.localizedGenericMessage + } +} +#endif + extension NCManageDatabase { #if !EXTENSION func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool { diff --git a/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift b/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift index 3af5768d52..a77d1f778a 100644 --- a/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift +++ b/iOSClient/GUI/Lucid Banner/ErrorBannerView.swift @@ -5,14 +5,18 @@ import SwiftUI import LucidBanner import NextcloudKit -import Alamofire @MainActor func showErrorBanner(windowScene: UIWindowScene?, - error: NKError) async { + error: NKError, + context: NCErrorContext = .generic) async { + guard let text = error.userFriendlyMessage(for: context) else { return } + + nkLog(error: "Error shown for \(context): \(error.errorCode) \(error.errorDescription)") + await showErrorBanner(windowScene: windowScene, - title: "_error_", - text: error.errorDescription, + title: context.failureTitle ?? "_error_", + text: text, errorCode: error.errorCode) } @@ -21,8 +25,7 @@ func showErrorBanner(windowScene: UIWindowScene?, title: String = "_error_", text: String, footnote: String? = nil, - errorCode: Int? = nil, - afError: AFError? = nil) async { + errorCode: Int? = nil) async { guard let windowScene, let window = windowScene.resolvedWindow else { return diff --git a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift index 030829e01b..7213f941ec 100644 --- a/iOSClient/Main/Collection Common/Cell/NCGridCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCGridCell.swift @@ -265,7 +265,7 @@ extension NCCollectionViewCommon { if !metadata.sessionError.isEmpty, metadata.status != global.metadataStatusNormal { cell.labelSubinfo.isHidden = false - cell.labelInfo.text = metadata.sessionError + cell.labelInfo.text = metadata.sessionErrorMessage } else { cell.labelSubinfo.isHidden = false cell.writeInfoDateSize(date: metadata.date, size: metadata.size) diff --git a/iOSClient/Main/Collection Common/Cell/NCListCell.swift b/iOSClient/Main/Collection Common/Cell/NCListCell.swift index 867c469eee..1e805421be 100755 --- a/iOSClient/Main/Collection Common/Cell/NCListCell.swift +++ b/iOSClient/Main/Collection Common/Cell/NCListCell.swift @@ -462,7 +462,7 @@ extension NCCollectionViewCommon { cell.labelSubinfo?.isHidden = true } else if !metadata.sessionError.isEmpty, metadata.status != global.metadataStatusNormal { cell.labelSubinfo?.isHidden = false - cell.labelInfo?.text = metadata.sessionError + cell.labelInfo?.text = metadata.sessionErrorMessage } else { cell.labelSubinfo?.isHidden = false cell.writeInfoDateSize(date: metadata.date, size: metadata.size) diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift index 8ceef5a33a..72cef04143 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+CollectionViewDelegate.swift @@ -81,7 +81,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegate { if results.nkError == .success || results.nkError == .cancelled { print("ok") } else { - await showErrorBanner(windowScene: windowScene, text: results.nkError.errorDescription, errorCode: results.nkError.errorCode) + await showErrorBanner(windowScene: windowScene, error: results.nkError, context: .download(fileName: metadata.fileNameView)) } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift index 1815800cf5..bcc08c2ecb 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+Search.swift @@ -4,7 +4,6 @@ import Foundation import NextcloudKit -import Alamofire extension NCCollectionViewCommon { @MainActor @@ -70,8 +69,7 @@ extension NCCollectionViewCommon { ) } else { await showErrorBanner(windowScene: windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode) + error: results.error) } collectionView.reloadData() @@ -112,9 +110,7 @@ extension NCCollectionViewCommon { if results.error != .success { await showErrorBanner(windowScene: windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode, - afError: results.error.error as? AFError) + error: results.error) } guard isSearchingMode, @@ -145,9 +141,7 @@ extension NCCollectionViewCommon { if results.error != .success { await showErrorBanner(windowScene: windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode, - afError: results.error.error as? AFError) + error: results.error) } guard isSearchingMode, @@ -204,9 +198,7 @@ extension NCCollectionViewCommon { if results.error != .success { await showErrorBanner(windowScene: windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode, - afError: results.error.error as? AFError) + error: results.error) } guard isSearchingMode, diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift index 279de6f3a5..23018ef96a 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+UIEditMenuInteractionDelegate.swift @@ -133,8 +133,8 @@ extension NCCollectionViewCommon: UIEditMenuInteractionDelegate { } else { Task { await showErrorBanner(windowScene: windowScene, - text: resultsUpload.error.errorDescription, - errorCode: resultsUpload.error.errorCode) + error: resultsUpload.error, + context: .upload(fileName: fileName)) } } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 156dfecf9d..4838a85b2f 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -872,7 +872,7 @@ extension NCCollectionViewCommon: NCTransferDelegate { Task { if error != .success, error.errorCode != global.errorResourceNotFound { - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error, context: NCErrorContext(networkingStatus: networkingStatus, fileName: fileName)) } guard session.account == account else { diff --git a/iOSClient/Main/Create/NCCreate.swift b/iOSClient/Main/Create/NCCreate.swift index 5217caab5f..0e03c5ab77 100644 --- a/iOSClient/Main/Create/NCCreate.swift +++ b/iOSClient/Main/Create/NCCreate.swift @@ -170,10 +170,10 @@ class NCCreate: NSObject { ) guard results.error == .success, let metadata = results.metadata else { let windowScene = SceneManager.shared.getWindowScene(controller: controller) + let error: NKError = results.error == .success ? .invalidData : results.error await showErrorBanner( windowScene: windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode + error: error ) return } diff --git a/iOSClient/Main/NCDragDrop.swift b/iOSClient/Main/NCDragDrop.swift index 39f53ad598..5e35094b87 100644 --- a/iOSClient/Main/NCDragDrop.swift +++ b/iOSClient/Main/NCDragDrop.swift @@ -244,8 +244,8 @@ class NCDragDrop: NSObject { } guard results.nkError == .success else { await showErrorBanner(windowScene: windowScene, - text: results.nkError.errorDescription, - errorCode: results.nkError.errorCode) + error: results.nkError, + context: .download(fileName: metadata.fileNameView)) break } } @@ -275,7 +275,8 @@ class NCDragDrop: NSObject { } guard results.error == .success else { await showErrorBanner(windowScene: windowScene, - text: results.error.errorDescription, errorCode: results.error.errorCode) + error: results.error, + context: .upload(fileName: metadata.fileNameView)) break } diff --git a/iOSClient/Menu/NCContextMenuComment.swift b/iOSClient/Menu/NCContextMenuComment.swift index 327fb318aa..568da4f435 100644 --- a/iOSClient/Menu/NCContextMenuComment.swift +++ b/iOSClient/Menu/NCContextMenuComment.swift @@ -59,7 +59,7 @@ class NCContextMenuComment: NSObject { NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataNCShare) } else { Task { @MainActor in - await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } @@ -85,7 +85,7 @@ class NCContextMenuComment: NSObject { (self.viewController as? NCActivity)?.loadComments() } else { Task { @MainActor in - await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, error: error) } } } diff --git a/iOSClient/Networking/NCErrorContext.swift b/iOSClient/Networking/NCErrorContext.swift new file mode 100644 index 0000000000..c2986e6097 --- /dev/null +++ b/iOSClient/Networking/NCErrorContext.swift @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +/// What the user was doing when an error happened, so the message can say what failed. +enum NCErrorContext: Equatable { + case generic + case upload(fileName: String) + case download(fileName: String) + case delete(fileName: String) + case rename + case moveOrCopy(fileName: String) + case createFolder(folderName: String) + case favorite(fileName: String) + + /// The context of a transfer event, from its `networkingStatus`. + init(networkingStatus: String, fileName: String) { + let global = NCGlobal.shared + + switch networkingStatus { + case global.networkingStatusUploading, global.networkingStatusUploaded: + self = .upload(fileName: fileName) + + case global.networkingStatusDownloading, global.networkingStatusDownloaded: + self = .download(fileName: fileName) + + case global.networkingStatusDelete: + self = .delete(fileName: fileName) + + case global.networkingStatusRename: + self = .rename + + case global.networkingStatusCopyMove: + self = .moveOrCopy(fileName: fileName) + + case global.networkingStatusCreateFolder: + self = .createFolder(folderName: fileName) + + case global.networkingStatusFavorite: + self = .favorite(fileName: fileName) + + default: + self = .generic + } + } + + /// The context of the operation a metadata is waiting for or failed at, from its metadata `status`. + init(metadataStatus: Int, fileName: String) { + let global = NCGlobal.shared + + switch metadataStatus { + case global.metadataStatusWaitUpload, global.metadataStatusUploading, global.metadataStatusUploadError: + self = .upload(fileName: fileName) + + case global.metadataStatusWaitDownload, global.metadataStatusDownloading, global.metadataStatusDownloadError: + self = .download(fileName: fileName) + + case global.metadataStatusWaitDelete: + self = .delete(fileName: fileName) + + case global.metadataStatusWaitRename: + self = .rename + + case global.metadataStatusWaitCopy, global.metadataStatusWaitMove: + self = .moveOrCopy(fileName: fileName) + + case global.metadataStatusWaitCreateFolder: + self = .createFolder(folderName: fileName) + + case global.metadataStatusWaitFavorite: + self = .favorite(fileName: fileName) + + default: + self = .generic + } + } + + /// A title saying what failed, e.g. "Couldn't upload “IMG_0042.jpg”"; `nil` for `generic`. + var failureTitle: String? { + switch self { + case .generic: + return nil + + case .upload(let fileName): + return String(format: NSLocalizedString("_error_title_upload_", comment: ""), fileName) + + case .download(let fileName): + return String(format: NSLocalizedString("_error_title_download_", comment: ""), fileName) + + case .delete(let fileName): + return String(format: NSLocalizedString("_error_title_delete_", comment: ""), fileName) + + case .rename: + return NSLocalizedString("_error_title_rename_", comment: "") + + case .moveOrCopy(let fileName): + return String(format: NSLocalizedString("_error_title_move_copy_", comment: ""), fileName) + + case .createFolder(let folderName): + return String(format: NSLocalizedString("_error_title_create_folder_", comment: ""), folderName) + + case .favorite(let fileName): + return String(format: NSLocalizedString("_error_title_favorite_", comment: ""), fileName) + } + } +} diff --git a/iOSClient/Networking/NCNetworking+TransferDelegate.swift b/iOSClient/Networking/NCNetworking+TransferDelegate.swift index 5ebe490f74..ef43f099c4 100644 --- a/iOSClient/Networking/NCNetworking+TransferDelegate.swift +++ b/iOSClient/Networking/NCNetworking+TransferDelegate.swift @@ -228,10 +228,11 @@ extension NCNetworking: NCTransferDelegate { let resultsFile = await NextcloudKit.shared.getFileFromFileIdAsync(fileId: fileId, account: account) guard resultsFile.error == .success, let file = resultsFile.file else { + let error = resultsFile.error == .success ? NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: "File not found") : resultsFile.error + Task { await showErrorBanner(windowScene: windowScene, - text: resultsFile.error.errorDescription, - errorCode: resultsFile.error.errorCode) + error: error) } return } diff --git a/iOSClient/Networking/NKError+UserFriendlyMessage.swift b/iOSClient/Networking/NKError+UserFriendlyMessage.swift new file mode 100644 index 0000000000..9d0841c666 --- /dev/null +++ b/iOSClient/Networking/NKError+UserFriendlyMessage.swift @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Milen Pivchev +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import NextcloudKit + +extension NKError { + /// The message to show users for this error, or `nil` when nothing should be shown (success or cancellation). + /// + /// Tries the app's own text, then the few operations where the generic message would be wrong, then + /// NextcloudKit's generic message. Keep `errorDescription` for logs. + func userFriendlyMessage(for context: NCErrorContext = .generic) -> String? { + guard let kind, kind != .cancelled, errorCode != NCGlobal.shared.errorTaskCancelled else { return nil } + + if let appMessage { + return appMessage + } + + if let operationMessage = operationMessage(for: context, kind: kind) { + return operationMessage + } + + return localizedGenericMessage + } + + /// Text the app wrote itself: a localization key, or the description of one of its own error codes. + private var appMessage: String? { + let localized = NSLocalizedString(errorDescription, comment: "") + + if localized != errorDescription { + return localized + } + + let global = NCGlobal.shared + let isAppError = (global.errorInternalError...global.errorE2EENotEnabled).contains(errorCode) + + return isAppError && !errorDescription.isEmpty ? errorDescription : nil + } + + private var isVirusDetected: Bool { + let global = NCGlobal.shared + let isRejectedUpload = errorCode == global.errorBadRequest || errorCode == global.errorUnsupportedMediaType + + return isRejectedUpload && errorDescription.localizedCaseInsensitiveContains("virus") + } + + /// Unique cases that require a specific non-generic error + private func operationMessage(for context: NCErrorContext, kind: Kind) -> String? { + switch (context, kind) { + case (.upload, .unknown) where isVirusDetected: + return NSLocalizedString("_virus_detect_", comment: "") + + case (.upload, .conflict) where errorCode == NCGlobal.shared.errorConflict: + return NSLocalizedString("_error_upload_no_folder_", comment: "") + + case (.createFolder, .conflict): + return NSLocalizedString("_error_create_folder_no_parent_", comment: "") + + default: + return nil + } + } +} diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index b857bfb611..1678d7e056 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -275,7 +275,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { self.tableView.reloadData() } else if error != .success { Task { - await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, error: error) } } else { print("[Error] The user has been changed during networking process.") @@ -317,7 +317,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { } } else if error != .success { Task { - await showErrorBanner(windowScene: self.windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: self.windowScene, error: error) } } else { print("[Error] The user has been changed during networking process.") diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index b837efe5fb..88669c4534 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -217,7 +217,7 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent if error != .success { Task { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error, context: NCErrorContext(networkingStatus: networkingStatus, fileName: fileName)) } } diff --git a/iOSClient/Settings/AutoUpload/Utility/NCAutoUploadCounter.swift b/iOSClient/Settings/AutoUpload/Utility/NCAutoUploadCounter.swift index 8228baac6b..e0c4ee269d 100644 --- a/iOSClient/Settings/AutoUpload/Utility/NCAutoUploadCounter.swift +++ b/iOSClient/Settings/AutoUpload/Utility/NCAutoUploadCounter.swift @@ -182,7 +182,7 @@ final class NCAutoUploadCounter { ascending: false, limit: 1 )?.first - let error = metadata?.sessionError.trimmingCharacters(in: .whitespacesAndNewlines) + let error = metadata?.sessionErrorMessage.trimmingCharacters(in: .whitespacesAndNewlines) if let error, !error.isEmpty { suspensionError = error diff --git a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift index 8493b99bc9..e47251389e 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEModel.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEModel.swift @@ -145,7 +145,7 @@ class NCManageE2EE: NSObject, ObservableObject, ViewOnAppearHandling, TOPasscode } await showErrorBanner( windowScene: windowScene, - text: error.errorDescription + error: error ) } catch { // fallback (non NKError) diff --git a/iOSClient/Settings/E2EE/NCManageE2EEView.swift b/iOSClient/Settings/E2EE/NCManageE2EEView.swift index 0d45c0facc..4b9ce81250 100644 --- a/iOSClient/Settings/E2EE/NCManageE2EEView.swift +++ b/iOSClient/Settings/E2EE/NCManageE2EEView.swift @@ -245,8 +245,7 @@ struct NCManageE2EEView: View { text: "E2E delete publicKey") } else { await showErrorBanner(windowScene: model.windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode) + error: results.error) } } } @@ -277,8 +276,7 @@ struct NCManageE2EEView: View { text: "E2E delete privateKey") } else { await showErrorBanner(windowScene: model.windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode) + error: results.error) } } } @@ -308,8 +306,7 @@ struct NCManageE2EEView: View { text: "E2E delete Keys from FS") } else { await showErrorBanner(windowScene: model.windowScene, - text: results.error.errorDescription, - errorCode: results.error.errorCode) + error: results.error) } } } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 4823b05018..bb6f2d0228 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -112,9 +112,6 @@ "_view_in_folder_" = "View in folder"; "_active_" = "Active"; "_disabled_" = "Disabled"; -"_compact_" = "Compact"; -"_normal_" = "Normal"; -"_verbose_" = "Verbose"; "_error_occurred_" = "An error occurred, please try again."; "_retry_" = "Retry"; "_images_" = "Images"; @@ -599,8 +596,6 @@ "_while_charging_" = "While charging"; "_keep_screen_awake_" = "Keep screen awake\nwhile transferring files"; "_error_not_found_" = "The requested resource could not be found"; -"_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource"; -"_error_precondition_" = "The server does not meet one of the preconditions of the requester"; "_downloading_" = "Downloading"; "_additional_options_" = "Additional options"; "_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; @@ -718,13 +713,9 @@ // MARK: Login poll -// MARK: File name validator (used by NextcloudKit, don't remove) +// MARK: File name validator "_file_name_validator_error_reserved_name_" = "\"%@\" is a forbidden name."; -"_file_name_validator_error_forbidden_file_extension_" = "\".%@\" is a forbidden file extension."; -"_file_name_validator_error_invalid_character_" = "Name contains an invalid character: \"%@\"."; -"_file_name_validator_error_space_" = "Name must not contain spaces at the beginning or end."; "_file_name_new_extension_" = "Changing the extension might cause this file to open in a different application."; -"_file_name_empty_" = "File name cannot be empty."; "_please_rename_file_" = "Please rename the file or folder."; "hidden_file_name_warning" = "Name will result in a hidden file."; @@ -879,3 +870,15 @@ "_seek_backward_10_" = "Go back 10 seconds"; "_seek_forward_10_" = "Go forward 10 seconds"; + +// MARK: Error messages + +"_error_title_upload_" = "Couldn't upload “%@”"; +"_error_title_download_" = "Couldn't download “%@”"; +"_error_title_delete_" = "Couldn't delete “%@”"; +"_error_title_rename_" = "Couldn't rename the item"; +"_error_title_move_copy_" = "Couldn't move or copy “%@”"; +"_error_title_create_folder_" = "Couldn't create the folder “%@”"; +"_error_title_favorite_" = "Couldn't update the favorite for “%@”"; +"_error_upload_no_folder_" = "The folder you're uploading to doesn't exist anymore."; +"_error_create_folder_no_parent_" = "The parent folder doesn't exist."; diff --git a/iOSClient/Terms of service/NCTermOfServiceModel.swift b/iOSClient/Terms of service/NCTermOfServiceModel.swift index e4d7a10861..a3432dc66b 100644 --- a/iOSClient/Terms of service/NCTermOfServiceModel.swift +++ b/iOSClient/Terms of service/NCTermOfServiceModel.swift @@ -59,7 +59,7 @@ class NCTermOfServiceModel: ObservableObject { } } else { let windowScene = SceneManager.shared.getWindowScene(controller: controller) - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error) } } self.dismissView = true diff --git a/iOSClient/Transfers/NCTransfersModel.swift b/iOSClient/Transfers/NCTransfersModel.swift index 6de7ef6891..2706d7f9fe 100644 --- a/iOSClient/Transfers/NCTransfersModel.swift +++ b/iOSClient/Transfers/NCTransfersModel.swift @@ -236,7 +236,7 @@ final class TransfersViewModel: ObservableObject, NCMetadataDownloadTransfersSuc status += " – " + NSLocalizedString("_retry_soon_", comment: "") } } - return (symbol, status, item.sessionError) + return (symbol, status, item.sessionErrorMessage) case global.metadataStatusNormal: return ("checkmark.circle", NSLocalizedString("_done_", comment: ""), sizeText) default: diff --git a/iOSClient/Trash/NCTrash+Networking.swift b/iOSClient/Trash/NCTrash+Networking.swift index 4338787036..093b7da858 100644 --- a/iOSClient/Trash/NCTrash+Networking.swift +++ b/iOSClient/Trash/NCTrash+Networking.swift @@ -55,7 +55,7 @@ extension NCTrash { let results = await NextcloudKit.shared.deleteFileOrFolderAsync(serverUrlFileName: serverUrlFileName, account: session.account) if results.error != .success { - await showErrorBanner(windowScene: self.windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(windowScene: self.windowScene, error: results.error) } await self.database.deleteTrashAsync(fileId: nil, account: session.account) await self.reloadDataSource() @@ -70,7 +70,7 @@ extension NCTrash { let results = await NextcloudKit.shared.deleteFileOrFolderAsync(serverUrlFileName: serverUrlFileName, account: session.account) if results.error != .success { - await showErrorBanner(windowScene: self.windowScene, text: results.error.errorDescription, errorCode: results.error.errorCode) + await showErrorBanner(windowScene: self.windowScene, error: results.error, context: .delete(fileName: result.trashbinFileName)) } await self.database.deleteTrashAsync(fileId: fileId, account: session.account) await self.reloadDataSource() diff --git a/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift index 572c566371..2f75e80e9b 100644 --- a/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift +++ b/iOSClient/Viewer/NCViewerDirectEditing/NCViewerDirectEditing.swift @@ -437,7 +437,7 @@ final class NCViewerDirectEditing: UIViewController, WKNavigationDelegate, WKScr guard error == .success, account == self.metadata.account else { Task { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error) } return } @@ -565,7 +565,7 @@ extension NCViewerDirectEditing: NCSelectDelegate { } else { Task { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error == .success ? .invalidData : error) } } } diff --git a/iOSClient/Viewer/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index e61cac09fa..b07fdb51c8 100644 --- a/iOSClient/Viewer/NCViewerProviderContextMenu.swift +++ b/iOSClient/Viewer/NCViewerProviderContextMenu.swift @@ -176,7 +176,7 @@ extension NCViewerProviderContextMenu: NCTransferDelegate { guard let self, ocId == metadata?.ocId, metadata?.isImage == true else { return } if error != .success { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error, context: NCErrorContext(networkingStatus: networkingStatus, fileName: fileName)) } switch networkingStatus { diff --git a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift index 36dab8777f..7a4479defa 100644 --- a/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift +++ b/iOSClient/Viewer/NCViewerQuickLook/NCViewerQuickLook.swift @@ -256,9 +256,12 @@ extension NCViewerQuickLook: QLPreviewControllerDataSource, QLPreviewControllerD } error = results.error + var errorContext = NCErrorContext.upload(fileName: fileName) + if error == .success { let results = await NCNetworking.shared.readFileAsync(serverUrlFileName: serverUrlFileName, account: metadata.account) error = results.error + errorContext = .generic if results.error == .success, let metadata = results.metadata { // clean dir @@ -279,7 +282,7 @@ extension NCViewerQuickLook: QLPreviewControllerDataSource, QLPreviewControllerD } if error != .success { - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error, context: errorContext) } self.dismiss(animated: true) diff --git a/iOSClient/Viewer/NCViewerRichdocuments/NCViewerRichdocuments.swift b/iOSClient/Viewer/NCViewerRichdocuments/NCViewerRichdocuments.swift index 2a77c68955..725961cf19 100644 --- a/iOSClient/Viewer/NCViewerRichdocuments/NCViewerRichdocuments.swift +++ b/iOSClient/Viewer/NCViewerRichdocuments/NCViewerRichdocuments.swift @@ -286,7 +286,7 @@ class NCViewerRichdocuments: UIViewController, WKNavigationDelegate, WKScriptMes } else { Task { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error) } } }) @@ -343,7 +343,7 @@ class NCViewerRichdocuments: UIViewController, WKNavigationDelegate, WKScriptMes } else { Task { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error == .success ? .invalidData : error) } } } @@ -361,7 +361,7 @@ class NCViewerRichdocuments: UIViewController, WKNavigationDelegate, WKScriptMes } else { Task { let windowScene = SceneManager.shared.getWindow(sceneIdentifier: self.sceneIdentifier)?.windowScene - await showErrorBanner(windowScene: windowScene, text: error.errorDescription, errorCode: error.errorCode) + await showErrorBanner(windowScene: windowScene, error: error == .success ? .invalidData : error) } } }