Sfoglia il codice sorgente

Version 5.1.0 (#2805)

V 5.1.0
Marino Faggiana 1 anno fa
parent
commit
0f24e373b1
100 ha cambiato i file con 4205 aggiunte e 3992 eliminazioni
  1. 1 1
      Brand/Database.swift
  2. 1 1
      Brand/NCBrand.swift
  3. 1 1
      File Provider Extension/FileProviderExtension+Actions.swift
  4. 5 5
      File Provider Extension/FileProviderExtension.swift
  5. 124 46
      Nextcloud.xcodeproj/project.pbxproj
  6. 36 8
      Share/NCShareExtension.swift
  7. 103 298
      iOSClient/AppDelegate.swift
  8. 8 10
      iOSClient/AudioRecorder/NCAudioRecorderViewController.storyboard
  9. 26 42
      iOSClient/AudioRecorder/NCAudioRecorderViewController.swift
  10. 2 2
      iOSClient/Data/NCManageDatabase+Capabilities.swift
  11. 13 12
      iOSClient/Data/NCManageDatabase+Directory.swift
  12. 8 8
      iOSClient/Data/NCManageDatabase+LocalFile.swift
  13. 175 0
      iOSClient/Data/NCManageDatabase+Metadata+Session.swift
  14. 57 120
      iOSClient/Data/NCManageDatabase+Metadata.swift
  15. 6 0
      iOSClient/Extensions/Array+Extension.swift
  16. 0 33
      iOSClient/Extensions/NSNotificationCenter+MainThread.h
  17. 0 45
      iOSClient/Extensions/NSNotificationCenter+MainThread.m
  18. 9 0
      iOSClient/Extensions/UIView+Extension.swift
  19. 4 0
      iOSClient/Extensions/View+Extension.swift
  20. 0 1
      iOSClient/Favorites/NCFavorite.swift
  21. 0 1
      iOSClient/Files/NCFiles.swift
  22. 0 1
      iOSClient/Groupfolders/NCGroupfolders.swift
  23. 422 111
      iOSClient/Main/Collection Common/NCCollectionViewCommon.swift
  24. 160 0
      iOSClient/Main/Collection Common/NCCollectionViewCommonSelectTabBar.swift
  25. 29 90
      iOSClient/Main/Collection Common/NCSelectableNavigationView.swift
  26. 0 112
      iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.storyboard
  27. 0 347
      iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.swift
  28. 0 165
      iOSClient/Main/Create cloud/NCCreateMenuAdd.swift
  29. 8 12
      iOSClient/Main/Create cloud/NCUploadAssets.swift
  30. 20 12
      iOSClient/Main/NCActionCenter.swift
  31. 1 1
      iOSClient/Main/NCMainTabBar.swift
  32. 244 0
      iOSClient/Main/NCPasscode.swift
  33. 2 1
      iOSClient/Main/NCPickerViewController.swift
  34. 0 56
      iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.swift
  35. 3 75
      iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.xib
  36. 56 13
      iOSClient/Media/NCMedia.swift
  37. 94 93
      iOSClient/Media/NCMediaCommandView.swift
  38. 21 22
      iOSClient/Media/NCMediaCommandView.xib
  39. 7 7
      iOSClient/Media/NCMediaDataSource.swift
  40. 15 0
      iOSClient/Media/NCMediaGridLayout.swift
  41. 108 0
      iOSClient/Media/NCMediaSelectTabBar.swift
  42. 30 17
      iOSClient/Menu/AppDelegate+Menu.swift
  43. 31 31
      iOSClient/Menu/NCCollectionViewCommon+Menu.swift
  44. 37 48
      iOSClient/Menu/NCContextMenu.swift
  45. 2 0
      iOSClient/Menu/NCMenu+FloatingPanel.swift
  46. 8 8
      iOSClient/Menu/NCMenu.storyboard
  47. 14 0
      iOSClient/Menu/NCMenu.swift
  48. 24 46
      iOSClient/Menu/NCMenuAction.swift
  49. 7 4
      iOSClient/Menu/NCOperationSaveLivePhoto.swift
  50. 3 3
      iOSClient/Menu/NCTrash+Menu.swift
  51. 26 67
      iOSClient/Menu/NCViewer+Menu.swift
  52. 0 2
      iOSClient/NCGlobal.swift
  53. 1 2
      iOSClient/NCImageCache.swift
  54. 5 5
      iOSClient/Networking/NCAutoUpload.swift
  55. 355 0
      iOSClient/Networking/NCNetworking+Download.swift
  56. 116 0
      iOSClient/Networking/NCNetworking+LivePhoto.swift
  57. 94 0
      iOSClient/Networking/NCNetworking+Synchronization.swift
  58. 550 0
      iOSClient/Networking/NCNetworking+Upload.swift
  59. 985 0
      iOSClient/Networking/NCNetworking+WebDAV.swift
  60. 20 1934
      iOSClient/Networking/NCNetworking.swift
  61. 1 5
      iOSClient/Networking/NCNetworkingCheckRemoteUser.swift
  62. 86 43
      iOSClient/Networking/NCNetworkingProcess.swift
  63. 6 5
      iOSClient/Networking/NCService.swift
  64. 0 1
      iOSClient/Offline/NCOffline.swift
  65. 0 1
      iOSClient/Recent/NCRecent.swift
  66. 9 0
      iOSClient/RichWorkspace/NCViewerRichWorkspaceWebView.swift
  67. 2 1
      iOSClient/Scan document/NCUploadScanDocument.swift
  68. 1 1
      iOSClient/Select/NCSelect.swift
  69. 1 6
      iOSClient/Settings/CCAdvanced.m
  70. 0 1
      iOSClient/Settings/CCManageAccount.m
  71. 0 1
      iOSClient/Settings/NCSettings.m
  72. 0 1
      iOSClient/Shares/NCShares.swift
  73. BIN
      iOSClient/Supporting Files/af.lproj/Localizable.strings
  74. BIN
      iOSClient/Supporting Files/an.lproj/Localizable.strings
  75. BIN
      iOSClient/Supporting Files/ar.lproj/Localizable.strings
  76. BIN
      iOSClient/Supporting Files/ast.lproj/Localizable.strings
  77. BIN
      iOSClient/Supporting Files/az.lproj/Localizable.strings
  78. BIN
      iOSClient/Supporting Files/be.lproj/Localizable.strings
  79. BIN
      iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings
  80. BIN
      iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings
  81. BIN
      iOSClient/Supporting Files/br.lproj/Localizable.strings
  82. BIN
      iOSClient/Supporting Files/bs.lproj/Localizable.strings
  83. BIN
      iOSClient/Supporting Files/ca.lproj/Localizable.strings
  84. BIN
      iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings
  85. BIN
      iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings
  86. BIN
      iOSClient/Supporting Files/da.lproj/Localizable.strings
  87. BIN
      iOSClient/Supporting Files/de.lproj/Localizable.strings
  88. BIN
      iOSClient/Supporting Files/el.lproj/Localizable.strings
  89. BIN
      iOSClient/Supporting Files/en-GB.lproj/Localizable.strings
  90. 22 7
      iOSClient/Supporting Files/en.lproj/Localizable.strings
  91. BIN
      iOSClient/Supporting Files/eo.lproj/Localizable.strings
  92. BIN
      iOSClient/Supporting Files/es-419.lproj/Localizable.strings
  93. BIN
      iOSClient/Supporting Files/es-AR.lproj/Localizable.strings
  94. BIN
      iOSClient/Supporting Files/es-CL.lproj/Localizable.strings
  95. BIN
      iOSClient/Supporting Files/es-CO.lproj/Localizable.strings
  96. BIN
      iOSClient/Supporting Files/es-CR.lproj/Localizable.strings
  97. BIN
      iOSClient/Supporting Files/es-DO.lproj/Localizable.strings
  98. BIN
      iOSClient/Supporting Files/es-EC.lproj/Localizable.strings
  99. BIN
      iOSClient/Supporting Files/es-GT.lproj/Localizable.strings
  100. BIN
      iOSClient/Supporting Files/es-HN.lproj/Localizable.strings

+ 1 - 1
Brand/Database.swift

@@ -26,4 +26,4 @@ import Foundation
 // Database Realm
 //
 let databaseName                    = "nextcloud.realm"
-let databaseSchemaVersion: UInt64   = 340
+let databaseSchemaVersion: UInt64   = 342

+ 1 - 1
Brand/NCBrand.swift

@@ -36,7 +36,7 @@ let userAgent: String = {
     }()
 
     @objc public var brand: String = "Nextcloud"
-    @objc public var textCopyrightNextcloudiOS: String = "Nextcloud Liquid for iOS %@ © 2023"
+    @objc public var textCopyrightNextcloudiOS: String = "Nextcloud Hydrogen for iOS %@ © 2024"
     @objc public var textCopyrightNextcloudServer: String = "Nextcloud Server %@"
     @objc public var loginBaseUrl: String = "https://cloud.nextcloud.com"
     @objc public var pushNotificationServerProxy: String = "https://push-notifications.nextcloud.com"

+ 1 - 1
File Provider Extension/FileProviderExtension+Actions.swift

@@ -207,7 +207,7 @@ extension FileProviderExtension {
 
                     _ = self.fpUtility.moveFile(self.utilityFileSystem.getDirectoryProviderStorageIconOcId(itemIdentifier.rawValue, etag: metadata.etag), toPath: self.utilityFileSystem.getDirectoryProviderStorageIconOcId(itemIdentifier.rawValue, etag: metadata.etag))
 
-                    NCManageDatabase.shared.setLocalFile(ocId: ocId, fileName: itemName, etag: nil)
+                    NCManageDatabase.shared.setLocalFile(ocId: ocId, fileName: itemName)
                 }
 
                 guard let parentItemIdentifier = self.fpUtility.getParentItemIdentifier(metadata: metadata) else {

+ 5 - 5
File Provider Extension/FileProviderExtension.swift

@@ -285,7 +285,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         let serverUrlFileName = metadata.serverUrl + "/" + fileName
         let fileNameLocalPath = url.path
 
-        if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, description: metadata.ocId, session: NCNetworking.shared.sessionManagerUploadBackgroundExtension) {
+        if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, session: NCNetworking.shared.sessionManagerUploadBackgroundExtension) {
 
             fileProviderData.shared.fileProviderManager.register(task, forItemWithIdentifier: NSFileProviderItemIdentifier(metadata.fileId)) { _ in }
         }
@@ -365,7 +365,7 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
                 let serverUrlFileName = tableDirectory.serverUrl + "/" + fileName
                 let fileNameLocalPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: fileName)
 
-                if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, description: ocIdTemp, session: NCNetworking.shared.sessionManagerUploadBackgroundExtension) {
+                if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: nil, dateModificationFile: nil, session: NCNetworking.shared.sessionManagerUploadBackgroundExtension) {
 
                     self.outstandingSessionTasks[URL(fileURLWithPath: fileNameLocalPath)] = task as URLSessionTask
 
@@ -379,10 +379,10 @@ class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
         }
     }
 
-    func uploadComplete(fileName: String, serverUrl: String, ocId: String?, etag: String?, date: NSDate?, size: Int64, description: String?, task: URLSessionTask, error: NKError) {
+    func uploadComplete(fileName: String, serverUrl: String, ocId: String?, etag: String?, date: NSDate?, size: Int64, fileNameLocalPath: String?, task: URLSessionTask, error: NKError) {
 
-        guard let ocIdTemp = description else { return }
-        guard let metadataTemp = NCManageDatabase.shared.getMetadataFromOcId(ocIdTemp) else { return }
+        guard let metadataTemp = NCManageDatabase.shared.getMetadataFromFileNameLocalPath(fileNameLocalPath) else { return }
+        let ocIdTemp = metadataTemp.ocId
         let metadata = tableMetadata.init(value: metadataTemp)
 
         let url = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp, fileNameView: fileName))

+ 124 - 46
Nextcloud.xcodeproj/project.pbxproj

@@ -84,6 +84,7 @@
 		F31F69502A2F707E00162F76 /* SwiftUIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F694F2A2F707E00162F76 /* SwiftUIView+Extensions.swift */; };
 		F31F69612A2F907800162F76 /* __Snapshots__ in Resources */ = {isa = PBXBuildFile; fileRef = F31F69602A2F907800162F76 /* __Snapshots__ */; };
 		F31F69642A2F929600162F76 /* PreviewSnapshots in Frameworks */ = {isa = PBXBuildFile; productRef = F31F69632A2F929600162F76 /* PreviewSnapshots */; };
+		F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */; };
 		F32ED5062A2F254400EABA81 /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; };
 		F33AAF9A2A60394C006ECCBD /* NCMoreUserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F33AAF992A60394C006ECCBD /* NCMoreUserCell.xib */; };
 		F343A4B32A1E01FF00DDA874 /* PHAsset+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4B22A1E01FF00DDA874 /* PHAsset+Extension.swift */; };
@@ -107,6 +108,7 @@
 		F359D86B2A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
 		F359D86C2A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
 		F359D86D2A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; };
+		F38F71252B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38F71242B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift */; };
 		F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */; };
 		F3953BD72A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */; };
 		F3A7AFC62A41AA82001FC89C /* BaseUIXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */; };
@@ -119,14 +121,11 @@
 		F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; };
 		F700510122DF63AC003A3356 /* NCShare.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F700510022DF63AC003A3356 /* NCShare.storyboard */; };
 		F700510522DF6A89003A3356 /* NCShare.swift in Sources */ = {isa = PBXBuildFile; fileRef = F700510422DF6A89003A3356 /* NCShare.swift */; };
-		F7020FCE2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7020FCD2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift */; };
 		F702F2CD25EE5B4F008F8E80 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */; };
 		F702F2CF25EE5B5C008F8E80 /* NCGlobal.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */; };
 		F702F2D025EE5B5C008F8E80 /* NCGlobal.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */; };
 		F702F2D125EE5B5C008F8E80 /* NCGlobal.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */; };
 		F702F2D225EE5B5C008F8E80 /* NCGlobal.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */; };
-		F702F2E625EE5C86008F8E80 /* NCAudioRecorderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2E425EE5C82008F8E80 /* NCAudioRecorderViewController.swift */; };
-		F702F2E725EE5C86008F8E80 /* NCAudioRecorderViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F702F2E525EE5C82008F8E80 /* NCAudioRecorderViewController.storyboard */; };
 		F702F2F125EE5CDB008F8E80 /* NCLogin.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F702F2F025EE5CDA008F8E80 /* NCLogin.storyboard */; };
 		F702F2F725EE5CED008F8E80 /* NCLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2F625EE5CEC008F8E80 /* NCLogin.swift */; };
 		F702F30125EE5D2C008F8E80 /* NYMnemonic.m in Sources */ = {isa = PBXBuildFile; fileRef = F702F2FD25EE5D2C008F8E80 /* NYMnemonic.m */; };
@@ -158,7 +157,7 @@
 		F70D7C3725FFBF82002B9E34 /* NCCollectionViewCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D7C3525FFBF81002B9E34 /* NCCollectionViewCommon.swift */; };
 		F70D87CF25EE6E58008CBBBD /* NCRenameFile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F70D87CD25EE6E58008CBBBD /* NCRenameFile.storyboard */; };
 		F70D87D025EE6E58008CBBBD /* NCRenameFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D87CE25EE6E58008CBBBD /* NCRenameFile.swift */; };
-		F70D8D8124A4A9BF000A5756 /* NCNetworkingProcessUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D8D8024A4A9BF000A5756 /* NCNetworkingProcessUpload.swift */; };
+		F70D8D8124A4A9BF000A5756 /* NCNetworkingProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */; };
 		F710D1F52405770F00A6033D /* NCViewerPDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = F710D1F42405770F00A6033D /* NCViewerPDF.swift */; };
 		F710D2022405826100A6033D /* NCViewer+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F710D2012405826100A6033D /* NCViewer+Menu.swift */; };
 		F710FC7A277B7D0000AA9FBF /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = F710FC79277B7D0000AA9FBF /* Realm */; };
@@ -248,6 +247,29 @@
 		F72FD3B8297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72FD3B4297ED49A00075D28 /* NCManageDatabase+E2EE.swift */; };
 		F72FD3B9297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72FD3B4297ED49A00075D28 /* NCManageDatabase+E2EE.swift */; };
 		F72FD3BA297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72FD3B4297ED49A00075D28 /* NCManageDatabase+E2EE.swift */; };
+		F7327E202B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */; };
+		F7327E212B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */; };
+		F7327E232B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */; };
+		F7327E242B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */; };
+		F7327E282B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E272B73A53400A462C7 /* NCNetworking+Upload.swift */; };
+		F7327E292B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E272B73A53400A462C7 /* NCNetworking+Upload.swift */; };
+		F7327E2B2B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E272B73A53400A462C7 /* NCNetworking+Upload.swift */; };
+		F7327E2C2B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E272B73A53400A462C7 /* NCNetworking+Upload.swift */; };
+		F7327E302B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */; };
+		F7327E312B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */; };
+		F7327E322B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */; };
+		F7327E332B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */; };
+		F7327E352B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E342B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift */; };
+		F7327E362B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E342B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift */; };
+		F7327E372B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E342B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift */; };
+		F7327E382B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E342B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift */; };
+		F7327E392B73B8D400A462C7 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AC1CAF28AB94490032D99F /* Array+Extension.swift */; };
+		F7327E3A2B73B8D500A462C7 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AC1CAF28AB94490032D99F /* Array+Extension.swift */; };
+		F7327E3B2B73B8D600A462C7 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AC1CAF28AB94490032D99F /* Array+Extension.swift */; };
+		F7327E3D2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E3C2B73B92800A462C7 /* NCNetworking+Synchronization.swift */; };
+		F7327E3E2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E3C2B73B92800A462C7 /* NCNetworking+Synchronization.swift */; };
+		F7327E3F2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E3C2B73B92800A462C7 /* NCNetworking+Synchronization.swift */; };
+		F7327E402B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7327E3C2B73B92800A462C7 /* NCNetworking+Synchronization.swift */; };
 		F732D23327CF8AED000B0F1B /* NCPlayerToolBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = F732D23227CF8AED000B0F1B /* NCPlayerToolBar.xib */; };
 		F733598125C1C188002ABA72 /* NCAskAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F733598025C1C188002ABA72 /* NCAskAuthorization.swift */; };
 		F7346E1228B0EF5B006CE2D2 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7346E1128B0EF5B006CE2D2 /* WidgetKit.framework */; };
@@ -258,6 +280,10 @@
 		F7346E2928B0FFF2006CE2D2 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F7346E2828B0FFF2006CE2D2 /* RealmSwift */; };
 		F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */ = {isa = PBXBuildFile; productRef = F734B06528E75C0100E180D5 /* TLPhotoPicker */; };
 		F7362A1F220C853A005101B5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7362A1E220C853A005101B5 /* LaunchScreen.storyboard */; };
+		F737DA992B7B864E0063BAFC /* TOPasscodeViewController.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F70B86822642CF5500ED5349 /* TOPasscodeViewController.xcframework */; };
+		F737DA9D2B7B893C0063BAFC /* NCPasscode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F737DA9C2B7B893C0063BAFC /* NCPasscode.swift */; };
+		F737DA9E2B7B893C0063BAFC /* NCPasscode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F737DA9C2B7B893C0063BAFC /* NCPasscode.swift */; };
+		F737DA9F2B7B8AB90063BAFC /* NCLoginNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F738D48F2756740100CD1D38 /* NCLoginNavigationController.swift */; };
 		F7381EE1218218C9000B1560 /* NCOffline.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7381EDA218218C9000B1560 /* NCOffline.swift */; };
 		F7381EE5218218C9000B1560 /* NCOffline.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7381EDE218218C9000B1560 /* NCOffline.storyboard */; };
 		F738D4902756740100CD1D38 /* NCLoginNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F738D48F2756740100CD1D38 /* NCLoginNavigationController.swift */; };
@@ -337,13 +363,13 @@
 		F73EF7ED2B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73EF7E62B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift */; };
 		F73F537F1E929C8500F8678D /* NCMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73F537E1E929C8500F8678D /* NCMore.swift */; };
 		F740BEF02A35C2AD00E9B6D5 /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EE66AC2A20B226009AE765 /* UILabel+Extension.swift */; };
+		F741C2242B6B9FD600E849BB /* NCMediaSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F741C2232B6B9FD600E849BB /* NCMediaSelectTabBar.swift */; };
 		F7434B3620E23FE000417916 /* NCManageDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */; };
 		F7434B3820E2400600417916 /* NCBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76B3CCD1EAE01BD00921AC9 /* NCBrand.swift */; };
 		F745B253222D88AE00346520 /* NCLoginQRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F745B252222D88AE00346520 /* NCLoginQRCode.swift */; };
 		F746EC51273906C40052598D /* NCViewCertificateDetails.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7BC287D26663F6C004D46C5 /* NCViewCertificateDetails.storyboard */; };
 		F746EC52273906C40052598D /* NCViewCertificateDetails.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7BC287D26663F6C004D46C5 /* NCViewCertificateDetails.storyboard */; };
 		F746EC53273906C50052598D /* NCViewCertificateDetails.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7BC287D26663F6C004D46C5 /* NCViewCertificateDetails.storyboard */; };
-		F747BA1F22354D2000971601 /* NCCreateFormUploadVoiceNote.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F747BA1E22354D2000971601 /* NCCreateFormUploadVoiceNote.storyboard */; };
 		F7490E6B29882A92009DCE94 /* NCGlobal.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */; };
 		F7490E6C29882AEA009DCE94 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; };
 		F7490E6E29882B56009DCE94 /* NCBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76B3CCD1EAE01BD00921AC9 /* NCBrand.swift */; };
@@ -461,6 +487,8 @@
 		F765F73225237E3F00391DBE /* NCRecent.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F765F73025237E3F00391DBE /* NCRecent.storyboard */; };
 		F76673ED22C901F6007ED366 /* FileProviderDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76673EC22C901F5007ED366 /* FileProviderDomain.swift */; };
 		F76673F022C90434007ED366 /* FileProviderUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76673EF22C90433007ED366 /* FileProviderUtility.swift */; };
+		F76687072B7D067400779E3F /* NCAudioRecorderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76687052B7D067400779E3F /* NCAudioRecorderViewController.swift */; };
+		F76687082B7D067400779E3F /* NCAudioRecorderViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F76687062B7D067400779E3F /* NCAudioRecorderViewController.storyboard */; };
 		F7682FE023C36B0500983A04 /* NCMainTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7682FDF23C36B0500983A04 /* NCMainTabBar.swift */; };
 		F769453C22E9CFFF000A798A /* NCShareUserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F769453B22E9CFFF000A798A /* NCShareUserCell.xib */; };
 		F769454022E9F077000A798A /* NCSharePaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = F769453F22E9F077000A798A /* NCSharePaging.swift */; };
@@ -530,8 +558,6 @@
 		F77ED59128C9CE9D00E24ED0 /* ToolbarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77ED59028C9CE9D00E24ED0 /* ToolbarData.swift */; };
 		F77ED59328C9CEA000E24ED0 /* ToolbarWidgetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77ED59228C9CEA000E24ED0 /* ToolbarWidgetProvider.swift */; };
 		F77ED59528C9CEA400E24ED0 /* ToolbarWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77ED59428C9CEA300E24ED0 /* ToolbarWidgetView.swift */; };
-		F78071091EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m in Sources */ = {isa = PBXBuildFile; fileRef = F78071081EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m */; };
-		F780710A1EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m in Sources */ = {isa = PBXBuildFile; fileRef = F78071081EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m */; };
 		F7817CF829801A3500FFBC65 /* Data+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7817CF729801A3500FFBC65 /* Data+Extension.swift */; };
 		F7817CF929801A3500FFBC65 /* Data+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7817CF729801A3500FFBC65 /* Data+Extension.swift */; };
 		F7817CFA29801A3500FFBC65 /* Data+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7817CF729801A3500FFBC65 /* Data+Extension.swift */; };
@@ -608,7 +634,7 @@
 		F78E2D6C29AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; };
 		F78F74342163757000C2ADAD /* NCTrash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F78F74332163757000C2ADAD /* NCTrash.storyboard */; };
 		F78F74362163781100C2ADAD /* NCTrash.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78F74352163781100C2ADAD /* NCTrash.swift */; };
-		F790110E21415BF600D7B136 /* NCViewerRichdocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = F790110D21415BF600D7B136 /* NCViewerRichdocument.swift */; };
+		F790110E21415BF600D7B136 /* NCViewerRichDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = F790110D21415BF600D7B136 /* NCViewerRichDocument.swift */; };
 		F793E59D28B761E7005E4B02 /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; };
 		F793E59E28B763C2005E4B02 /* NCAskAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F733598025C1C188002ABA72 /* NCAskAuthorization.swift */; };
 		F793E59F28B764F6005E4B02 /* NCContentPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F765608E23BF813500765969 /* NCContentPresenter.swift */; };
@@ -673,6 +699,13 @@
 		F7B398432A6A91D5007538D6 /* NCSectionHeaderMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7B398412A6A91D5007538D6 /* NCSectionHeaderMenu.xib */; };
 		F7B6B70427C4E7FA00A7F6EB /* NCScan+CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B6B70327C4E7FA00A7F6EB /* NCScan+CollectionView.swift */; };
 		F7B7504B2397D38F004E13EC /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B7504A2397D38E004E13EC /* UIImage+Extension.swift */; };
+		F7B769A82B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; };
+		F7B769A92B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; };
+		F7B769AA2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; };
+		F7B769AB2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; };
+		F7B769AC2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; };
+		F7B769AD2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; };
+		F7B769AE2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */; };
 		F7B8B83025681C3400967775 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F7B8B82F25681C3400967775 /* GoogleService-Info.plist */; };
 		F7BAADCB1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */; };
 		F7BAADCC1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */; };
@@ -936,10 +969,12 @@
 		F31F69442A2F6D4600162F76 /* NextcloudSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudSnapshotTests.swift; sourceTree = "<group>"; };
 		F31F694F2A2F707E00162F76 /* SwiftUIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftUIView+Extensions.swift"; sourceTree = "<group>"; };
 		F31F69602A2F907800162F76 /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = "<group>"; };
+		F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTrashSelectTabBar.swift; sourceTree = "<group>"; };
 		F33AAF992A60394C006ECCBD /* NCMoreUserCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCMoreUserCell.xib; sourceTree = "<group>"; };
 		F343A4B22A1E01FF00DDA874 /* PHAsset+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHAsset+Extension.swift"; sourceTree = "<group>"; };
 		F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = "<group>"; };
 		F359D8662A7D03420023F405 /* NCUtility+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCUtility+Exif.swift"; sourceTree = "<group>"; };
+		F38F71242B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCollectionViewCommonSelectTabBar.swift; sourceTree = "<group>"; };
 		F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNCMoreCell.swift; sourceTree = "<group>"; };
 		F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseIntegrationXCTestCase.swift; sourceTree = "<group>"; };
 		F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUIXCTestCase.swift; sourceTree = "<group>"; };
@@ -951,7 +986,6 @@
 		F700510022DF63AC003A3356 /* NCShare.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShare.storyboard; sourceTree = "<group>"; };
 		F700510222DF6897003A3356 /* Parchment.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Parchment.framework; path = Carthage/Build/iOS/Parchment.framework; sourceTree = "<group>"; };
 		F700510422DF6A89003A3356 /* NCShare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShare.swift; sourceTree = "<group>"; };
-		F7020FCD2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCCreateFormUploadVoiceNote.swift; sourceTree = "<group>"; };
 		F702864D27735D1400ADA8BE /* libavdevice.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libavdevice.xcframework; path = ffmpeg/libavdevice.xcframework; sourceTree = "<group>"; };
 		F702864E27735D1400ADA8BE /* ffmpegkit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ffmpegkit.xcframework; path = ffmpeg/ffmpegkit.xcframework; sourceTree = "<group>"; };
 		F702864F27735D1500ADA8BE /* libnettle.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = libnettle.xcframework; path = ffmpeg/libnettle.xcframework; sourceTree = "<group>"; };
@@ -969,8 +1003,6 @@
 		F702867E2773609C00ADA8BE /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
 		F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCGlobal.swift; sourceTree = "<group>"; };
-		F702F2E425EE5C82008F8E80 /* NCAudioRecorderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCAudioRecorderViewController.swift; sourceTree = "<group>"; };
-		F702F2E525EE5C82008F8E80 /* NCAudioRecorderViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCAudioRecorderViewController.storyboard; sourceTree = "<group>"; };
 		F702F2F025EE5CDA008F8E80 /* NCLogin.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCLogin.storyboard; sourceTree = "<group>"; };
 		F702F2F625EE5CEC008F8E80 /* NCLogin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCLogin.swift; sourceTree = "<group>"; };
 		F702F2FD25EE5D2C008F8E80 /* NYMnemonic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NYMnemonic.m; sourceTree = "<group>"; };
@@ -1017,7 +1049,7 @@
 		F70D7C3525FFBF81002B9E34 /* NCCollectionViewCommon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCCollectionViewCommon.swift; sourceTree = "<group>"; };
 		F70D87CD25EE6E58008CBBBD /* NCRenameFile.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCRenameFile.storyboard; sourceTree = "<group>"; };
 		F70D87CE25EE6E58008CBBBD /* NCRenameFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCRenameFile.swift; sourceTree = "<group>"; };
-		F70D8D8024A4A9BF000A5756 /* NCNetworkingProcessUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingProcessUpload.swift; sourceTree = "<group>"; };
+		F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingProcess.swift; sourceTree = "<group>"; };
 		F70F2BA4225F2D8900EBB73E /* ZIPFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZIPFoundation.framework; path = Carthage/Build/iOS/ZIPFoundation.framework; sourceTree = "<group>"; };
 		F70F96AF2874394B006C8379 /* Nextcloud-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Nextcloud-Bridging-Header.h"; sourceTree = "<group>"; };
 		F710C5EF2471A6D1009AD8B7 /* Sentry.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sentry.framework; path = Carthage/Build/iOS/Sentry.framework; sourceTree = "<group>"; };
@@ -1080,6 +1112,11 @@
 		F72FD3B4297ED49A00075D28 /* NCManageDatabase+E2EE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+E2EE.swift"; sourceTree = "<group>"; };
 		F7320934201B812F008A0888 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
 		F732093B201B81E4008A0888 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
+		F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+Download.swift"; sourceTree = "<group>"; };
+		F7327E272B73A53400A462C7 /* NCNetworking+Upload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+Upload.swift"; sourceTree = "<group>"; };
+		F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+WebDAV.swift"; sourceTree = "<group>"; };
+		F7327E342B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+LivePhoto.swift"; sourceTree = "<group>"; };
+		F7327E3C2B73B92800A462C7 /* NCNetworking+Synchronization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+Synchronization.swift"; sourceTree = "<group>"; };
 		F732D23227CF8AED000B0F1B /* NCPlayerToolBar.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCPlayerToolBar.xib; sourceTree = "<group>"; };
 		F733598025C1C188002ABA72 /* NCAskAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAskAuthorization.swift; sourceTree = "<group>"; };
 		F733B65121997CC1001C1FFA /* TLPhotoPicker.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TLPhotoPicker.framework; path = Carthage/Build/iOS/TLPhotoPicker.framework; sourceTree = "<group>"; };
@@ -1090,6 +1127,7 @@
 		F7346E2228B0FEBA006CE2D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		F7362A1E220C853A005101B5 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
 		F736B551234DCF57008A5C9F /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
+		F737DA9C2B7B893C0063BAFC /* NCPasscode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCPasscode.swift; sourceTree = "<group>"; };
 		F7381EDA218218C9000B1560 /* NCOffline.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCOffline.swift; sourceTree = "<group>"; };
 		F7381EDE218218C9000B1560 /* NCOffline.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCOffline.storyboard; sourceTree = "<group>"; };
 		F738D48F2756740100CD1D38 /* NCLoginNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCLoginNavigationController.swift; sourceTree = "<group>"; };
@@ -1108,11 +1146,11 @@
 		F73EF7DE2B02266C0087E6E9 /* NCManageDatabase+Trash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Trash.swift"; sourceTree = "<group>"; };
 		F73EF7E62B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+UserStatus.swift"; sourceTree = "<group>"; };
 		F73F537E1E929C8500F8678D /* NCMore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCMore.swift; sourceTree = "<group>"; };
+		F741C2232B6B9FD600E849BB /* NCMediaSelectTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCMediaSelectTabBar.swift; sourceTree = "<group>"; };
 		F7421EAE2294044B00C4B7C1 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
 		F7434B5F20E2440600417916 /* FileProviderExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FileProviderExtension-Bridging-Header.h"; sourceTree = "<group>"; };
 		F745B250222D871800346520 /* QRCodeReader.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QRCodeReader.framework; path = Carthage/Build/iOS/QRCodeReader.framework; sourceTree = "<group>"; };
 		F745B252222D88AE00346520 /* NCLoginQRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCLoginQRCode.swift; sourceTree = "<group>"; };
-		F747BA1E22354D2000971601 /* NCCreateFormUploadVoiceNote.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCCreateFormUploadVoiceNote.storyboard; sourceTree = "<group>"; };
 		F749B649297B0CBB00087535 /* NCManageDatabase+Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Share.swift"; sourceTree = "<group>"; };
 		F749B650297B0F2400087535 /* NCManageDatabase+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Avatar.swift"; sourceTree = "<group>"; };
 		F74AF3A3247FB6AE00AC767B /* NCUtilityFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCUtilityFileSystem.swift; sourceTree = "<group>"; };
@@ -1167,6 +1205,8 @@
 		F765F73025237E3F00391DBE /* NCRecent.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCRecent.storyboard; sourceTree = "<group>"; };
 		F76673EC22C901F5007ED366 /* FileProviderDomain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileProviderDomain.swift; sourceTree = "<group>"; };
 		F76673EF22C90433007ED366 /* FileProviderUtility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileProviderUtility.swift; sourceTree = "<group>"; };
+		F76687052B7D067400779E3F /* NCAudioRecorderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCAudioRecorderViewController.swift; sourceTree = "<group>"; };
+		F76687062B7D067400779E3F /* NCAudioRecorderViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCAudioRecorderViewController.storyboard; sourceTree = "<group>"; };
 		F7682FDF23C36B0500983A04 /* NCMainTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMainTabBar.swift; sourceTree = "<group>"; };
 		F769453B22E9CFFF000A798A /* NCShareUserCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCShareUserCell.xib; sourceTree = "<group>"; };
 		F769453F22E9F077000A798A /* NCSharePaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSharePaging.swift; sourceTree = "<group>"; };
@@ -1229,8 +1269,6 @@
 		F77ED59028C9CE9D00E24ED0 /* ToolbarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarData.swift; sourceTree = "<group>"; };
 		F77ED59228C9CEA000E24ED0 /* ToolbarWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarWidgetProvider.swift; sourceTree = "<group>"; };
 		F77ED59428C9CEA300E24ED0 /* ToolbarWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarWidgetView.swift; sourceTree = "<group>"; };
-		F78071071EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+MainThread.h"; sourceTree = "<group>"; };
-		F78071081EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSNotificationCenter+MainThread.m"; sourceTree = "<group>"; };
 		F7817CF729801A3500FFBC65 /* Data+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extension.swift"; sourceTree = "<group>"; };
 		F783030E28B4C83F00B84583 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
 		F783031028B4C86200B84583 /* libc++.1.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.1.tbd"; path = "usr/lib/libc++.1.tbd"; sourceTree = SDKROOT; };
@@ -1260,7 +1298,7 @@
 		F78E2D6429AF02DB0024D4F3 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
 		F78F74332163757000C2ADAD /* NCTrash.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCTrash.storyboard; sourceTree = "<group>"; };
 		F78F74352163781100C2ADAD /* NCTrash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTrash.swift; sourceTree = "<group>"; };
-		F790110D21415BF600D7B136 /* NCViewerRichdocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerRichdocument.swift; sourceTree = "<group>"; };
+		F790110D21415BF600D7B136 /* NCViewerRichDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerRichDocument.swift; sourceTree = "<group>"; };
 		F79018A424092EF4007C9B6D /* ATGMediaBrowser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ATGMediaBrowser.framework; path = Carthage/Build/iOS/ATGMediaBrowser.framework; sourceTree = "<group>"; };
 		F79131C628AFB86E00577277 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = "<group>"; };
 		F79131C728AFB86E00577277 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -1346,6 +1384,7 @@
 		F7B398412A6A91D5007538D6 /* NCSectionHeaderMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCSectionHeaderMenu.xib; sourceTree = "<group>"; };
 		F7B6B70327C4E7FA00A7F6EB /* NCScan+CollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCScan+CollectionView.swift"; sourceTree = "<group>"; };
 		F7B7504A2397D38E004E13EC /* UIImage+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = "<group>"; };
+		F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Metadata+Session.swift"; sourceTree = "<group>"; };
 		F7B8B82F25681C3400967775 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; };
 		F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCManageDatabase.swift; sourceTree = "<group>"; };
 		F7BB04851FD58ACB00BBFD2A /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = "<group>"; };
@@ -1539,6 +1578,7 @@
 				F710FC80277B7D2700AA9FBF /* RealmSwift in Frameworks */,
 				F74C863D2AEFBFD9009A1D4A /* LRUCache in Frameworks */,
 				F72AD70F28C24BA1006CB92D /* NextcloudKit in Frameworks */,
+				F737DA992B7B864E0063BAFC /* TOPasscodeViewController.xcframework in Frameworks */,
 				F72CD01227A7E92400E59476 /* JGProgressHUD in Frameworks */,
 				F77CB6A92AA08053000C3CA4 /* OpenSSL in Frameworks */,
 				F760DE092AE66ED00027D78A /* KeychainAccess in Frameworks */,
@@ -1760,7 +1800,6 @@
 		F70211F31BAC56E9003FC03E /* Main */ = {
 			isa = PBXGroup;
 			children = (
-				F702F2E325EE5C82008F8E80 /* AudioRecorder */,
 				F7DFB7E9219C5A0500680748 /* Create cloud */,
 				F78ACD50219046AC0088454D /* Section Header Footer */,
 				F7603298252F0E550015A421 /* Collection Common */,
@@ -1769,19 +1808,11 @@
 				F7682FDF23C36B0500983A04 /* NCMainTabBar.swift */,
 				F75B0ABC244C4DBB00E58DCA /* NCActionCenter.swift */,
 				F77444F7222816D5000D5EB0 /* NCPickerViewController.swift */,
+				F737DA9C2B7B893C0063BAFC /* NCPasscode.swift */,
 			);
 			path = Main;
 			sourceTree = "<group>";
 		};
-		F702F2E325EE5C82008F8E80 /* AudioRecorder */ = {
-			isa = PBXGroup;
-			children = (
-				F702F2E425EE5C82008F8E80 /* NCAudioRecorderViewController.swift */,
-				F702F2E525EE5C82008F8E80 /* NCAudioRecorderViewController.storyboard */,
-			);
-			path = AudioRecorder;
-			sourceTree = "<group>";
-		};
 		F702F2FC25EE5D2C008F8E80 /* NYMnemonic */ = {
 			isa = PBXGroup;
 			children = (
@@ -1860,7 +1891,7 @@
 		F7239861253C95D500257F49 /* NCViewerRichdocument */ = {
 			isa = PBXGroup;
 			children = (
-				F790110D21415BF600D7B136 /* NCViewerRichdocument.swift */,
+				F790110D21415BF600D7B136 /* NCViewerRichDocument.swift */,
 				F723985B253C95CE00257F49 /* NCViewerRichdocument.storyboard */,
 			);
 			path = NCViewerRichdocument;
@@ -1982,11 +2013,16 @@
 			children = (
 				F7C30DF8291BCBF00017149B /* E2EE */,
 				F72CD63925C19EBF00F46F9A /* NCAutoUpload.swift */,
+				F77BC3EC293E528A005F2B08 /* NCConfigServer.swift */,
 				F75A9EE523796C6F0044CFCE /* NCNetworking.swift */,
+				F7327E1F2B73A42F00A462C7 /* NCNetworking+Download.swift */,
+				F7327E272B73A53400A462C7 /* NCNetworking+Upload.swift */,
+				F7327E2F2B73A86700A462C7 /* NCNetworking+WebDAV.swift */,
+				F7327E342B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift */,
+				F7327E3C2B73B92800A462C7 /* NCNetworking+Synchronization.swift */,
 				F7D96FCB246ED7E100536D73 /* NCNetworkingCheckRemoteUser.swift */,
-				F70D8D8024A4A9BF000A5756 /* NCNetworkingProcessUpload.swift */,
+				F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */,
 				F755BD9A20594AC7008C5FBB /* NCService.swift */,
-				F77BC3EC293E528A005F2B08 /* NCConfigServer.swift */,
 			);
 			path = Networking;
 			sourceTree = "<group>";
@@ -2030,6 +2066,7 @@
 				F78ACD4521903D010088454D /* NCGridCell.xib */,
 				F78ACD4121903CE00088454D /* NCListCell.swift */,
 				F78ACD4321903CF20088454D /* NCListCell.xib */,
+				F38F71242B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift */,
 			);
 			path = "Collection Common";
 			sourceTree = "<group>";
@@ -2054,6 +2091,15 @@
 			path = Recent;
 			sourceTree = "<group>";
 		};
+		F76687042B7D067400779E3F /* AudioRecorder */ = {
+			isa = PBXGroup;
+			children = (
+				F76687052B7D067400779E3F /* NCAudioRecorderViewController.swift */,
+				F76687062B7D067400779E3F /* NCAudioRecorderViewController.storyboard */,
+			);
+			path = AudioRecorder;
+			sourceTree = "<group>";
+		};
 		F769CA1B2966EF4F00039397 /* GUI */ = {
 			isa = PBXGroup;
 			children = (
@@ -2146,6 +2192,7 @@
 				F78F74332163757000C2ADAD /* NCTrash.storyboard */,
 				F78F74352163781100C2ADAD /* NCTrash.swift */,
 				AF3FDCC12796ECC300710F60 /* NCTrash+CollectionView.swift */,
+				F321DA892B71205A00DDA0E6 /* NCTrashSelectTabBar.swift */,
 			);
 			path = Trash;
 			sourceTree = "<group>";
@@ -2213,8 +2260,6 @@
 				F7817CF729801A3500FFBC65 /* Data+Extension.swift */,
 				F70460512499061800BB98A7 /* NotificationCenter+MainThread.swift */,
 				F79B869A265E19D40085C0E0 /* NSMutableAttributedString+Extension.swift */,
-				F78071071EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.h */,
-				F78071081EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m */,
 				AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */,
 				F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */,
 				F343A4B22A1E01FF00DDA874 /* PHAsset+Extension.swift */,
@@ -2315,6 +2360,7 @@
 				F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */,
 				F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */,
 				AF4BF61827562A4B0081CEEF /* NCManageDatabase+Metadata.swift */,
+				F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */,
 				F73EF7C62B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift */,
 				F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */,
 				F749B649297B0CBB00087535 /* NCManageDatabase+Share.swift */,
@@ -2471,8 +2517,6 @@
 				F704B5E62430C06700632F5F /* NCCreateFormUploadConflictCell.xib */,
 				F7651A8823A2A3F2001403D2 /* NCCreateFormUploadDocuments.storyboard */,
 				F7651A8923A2A3F2001403D2 /* NCCreateFormUploadDocuments.swift */,
-				F747BA1E22354D2000971601 /* NCCreateFormUploadVoiceNote.storyboard */,
-				F7020FCD2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift */,
 			);
 			path = "Create cloud";
 			sourceTree = "<group>";
@@ -2499,6 +2543,7 @@
 				F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */,
 				F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnaill.swift */,
 				F7BD50302B65216300D5AEF9 /* NCMediaGridLayout.swift */,
+				F741C2232B6B9FD600E849BB /* NCMediaSelectTabBar.swift */,
 			);
 			path = Media;
 			sourceTree = "<group>";
@@ -2580,6 +2625,7 @@
 				F70211F31BAC56E9003FC03E /* Main */,
 				F7CA213725F1372B00826ABB /* Account Request */,
 				F7A321621E9E37960069AD1B /* Activity */,
+				F76687042B7D067400779E3F /* AudioRecorder */,
 				F7AE00F6230E8191007ACF8A /* BrowserWeb */,
 				F70B866A2642A21300ED5349 /* Color */,
 				F7BAAD951ED5A63D00B7EAD4 /* Data */,
@@ -3322,7 +3368,6 @@
 				F79A65C32191D90F00FF6DCC /* NCSelect.storyboard in Resources */,
 				F7226EDC1EE4089300EBECB1 /* Main.storyboard in Resources */,
 				F31F69612A2F907800162F76 /* __Snapshots__ in Resources */,
-				F702F2E725EE5C86008F8E80 /* NCAudioRecorderViewController.storyboard in Resources */,
 				AF56C1DC2784856200D8BAE2 /* NCActivityCommentView.xib in Resources */,
 				F7F4F10B27ECDBDB008676F9 /* Inconsolata-Light.ttf in Resources */,
 				3704EB2A23D5A58400455C5B /* NCMenu.storyboard in Resources */,
@@ -3351,7 +3396,6 @@
 				F7A60F87292D215000FCE1F2 /* NCShareAccounts.storyboard in Resources */,
 				F7239877253D86D300257F49 /* NCEmptyView.xib in Resources */,
 				F761856A29E98543006EB3B0 /* NCIntro.storyboard in Resources */,
-				F747BA1F22354D2000971601 /* NCCreateFormUploadVoiceNote.storyboard in Resources */,
 				F719D9E0288D37A300762E33 /* NCColorPicker.storyboard in Resources */,
 				F7651A8A23A2A3F2001403D2 /* NCCreateFormUploadDocuments.storyboard in Resources */,
 				F7F4F10A27ECDBDB008676F9 /* Inconsolata-ExtraBold.ttf in Resources */,
@@ -3380,6 +3424,7 @@
 				F74DE14425135B6800917068 /* NCTransfers.storyboard in Resources */,
 				F77910A525DD517B00CEDB9E /* Settings.bundle in Resources */,
 				F7CB68A0254169530050EC94 /* NCSettings.storyboard in Resources */,
+				F76687082B7D067400779E3F /* NCAudioRecorderViewController.storyboard in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -3474,6 +3519,7 @@
 				F79B646326CA661600838ACA /* UIControl+Extension.swift in Sources */,
 				F73EF7B52B0224350087E6E9 /* NCManageDatabase+DirectEditing.swift in Sources */,
 				F78A10C429322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */,
+				F7B769AE2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */,
 				F75CA1482962F13700B01130 /* HUDView.swift in Sources */,
 				F711A4E22AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */,
 				AF4BF61C27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */,
@@ -3573,6 +3619,7 @@
 				F7490E8729882CA8009DCE94 /* ThreadSafeDictionary.swift in Sources */,
 				F73EF7A42B021FAD0087E6E9 /* NCLivePhoto.swift in Sources */,
 				F757CC8729E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */,
+				F7B769AD2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */,
 				F73EF7BC2B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */,
 				F73EF7B42B0224350087E6E9 /* NCManageDatabase+DirectEditing.swift in Sources */,
 				F7490E8229882C80009DCE94 /* NCManageDatabase+E2EE.swift in Sources */,
@@ -3598,8 +3645,10 @@
 				AF4BF61F27562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */,
 				F7A0D1362591FBC5008F8A13 /* String+Extension.swift in Sources */,
 				F7EDE4D6262D7B9600414FE6 /* NCListCell.swift in Sources */,
+				F7327E372B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */,
 				F73EF7D22B0225BA0087E6E9 /* NCManageDatabase+Tag.swift in Sources */,
 				F74B6D982A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */,
+				F737DA9F2B7B8AB90063BAFC /* NCLoginNavigationController.swift in Sources */,
 				F7707687263A853700A1BA94 /* NCContentPresenter.swift in Sources */,
 				F343A4B62A1E084200DDA874 /* PHAsset+Extension.swift in Sources */,
 				F70460532499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */,
@@ -3607,6 +3656,7 @@
 				AF22B20C277C6F4D00DAB0CC /* NCShareCell.swift in Sources */,
 				F73EF7EA2B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift in Sources */,
 				F359D86A2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
+				F7B769AB2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */,
 				F7E98C1727E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */,
 				F79B646126CA661600838ACA /* UIControl+Extension.swift in Sources */,
 				F77C973A2953143A00FDDD09 /* NCCameraRoll.swift in Sources */,
@@ -3617,6 +3667,7 @@
 				AF4BF615275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */,
 				F798F0E225880608000DAFFD /* UIColor+Extension.swift in Sources */,
 				AF3FDCC32796F3FB00710F60 /* NCTrashListCell+NCTrashCellProtocol.swift in Sources */,
+				F7327E3F2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */,
 				F7C9B9202B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */,
 				AF817EF2274BC781009ED85B /* NCUserBaseUrl.swift in Sources */,
 				F78E2D6829AF02DB0024D4F3 /* Database.swift in Sources */,
@@ -3628,6 +3679,7 @@
 				AF22B206277B4E4C00DAB0CC /* NCCreateFormUploadConflict.swift in Sources */,
 				F7BD71E62636EAFC00643C34 /* NCNetworkingE2EE.swift in Sources */,
 				F7F878AF1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */,
+				F7327E3B2B73B8D600A462C7 /* Array+Extension.swift in Sources */,
 				AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */,
 				F702F2D025EE5B5C008F8E80 /* NCGlobal.swift in Sources */,
 				F343A4BE2A1E734600DDA874 /* Optional+Extension.swift in Sources */,
@@ -3640,6 +3692,7 @@
 				F75A9EE723796C6F0044CFCE /* NCNetworking.swift in Sources */,
 				AF730AFA27843E4C00B7520E /* NCShareExtension+NCDelegate.swift in Sources */,
 				F7183BD62AEBDCD7000CD020 /* NCKeychain.swift in Sources */,
+				F7327E232B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */,
 				F749B64D297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
 				F72FD3B8297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */,
 				F7EDE4E0262D7BAF00414FE6 /* NCGridCell.swift in Sources */,
@@ -3648,6 +3701,7 @@
 				F73EF7AA2B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */,
 				F763D2A02A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */,
 				F757CC8529E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */,
+				F737DA9E2B7B893C0063BAFC /* NCPasscode.swift in Sources */,
 				F7817D0129802D5F00FFBC65 /* NCViewCertificateDetails.swift in Sources */,
 				F7D57C8B26317BDE00DE301D /* NCAccountRequest.swift in Sources */,
 				F7C30DF7291BC0D30017149B /* NCNetworkingE2EEUpload.swift in Sources */,
@@ -3660,15 +3714,16 @@
 				F76D364728A4F8BF00214537 /* NCActivityIndicator.swift in Sources */,
 				F73EF7CA2B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */,
 				F749B654297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */,
-				F780710A1EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m in Sources */,
 				F79EC77F26316193004E59D6 /* NCRenameFile.swift in Sources */,
 				AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */,
 				F74C86382AEFBE64009A1D4A /* NCImageCache.swift in Sources */,
 				F73EF7C22B02250B0087E6E9 /* NCManageDatabase+GPS.swift in Sources */,
+				F7327E2B2B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */,
 				F7148041262EBE4000693E51 /* NCShareExtension.swift in Sources */,
 				F76B3CCF1EAE01BD00921AC9 /* NCBrand.swift in Sources */,
 				F72944F32A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */,
 				F7BAADCC1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */,
+				F7327E322B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -3706,7 +3761,9 @@
 				F76DEE9828F808AF0041B1C9 /* LockscreenWidgetProvider.swift in Sources */,
 				F78A10C029322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */,
 				F78302FA28B4C3EA00B84583 /* NCManageDatabase+Metadata.swift in Sources */,
+				F7B769A92B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */,
 				F78E2D6629AF02DB0024D4F3 /* Database.swift in Sources */,
+				F7327E3E2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */,
 				F73EF7C82B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */,
 				F783030728B4C52800B84583 /* UIColor+Extension.swift in Sources */,
 				F783030028B4C45800B84583 /* NCGlobal.swift in Sources */,
@@ -3714,6 +3771,8 @@
 				F7BF9D832934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */,
 				F757CC8329E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */,
 				F73EF7A82B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */,
+				F7327E212B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */,
+				F7327E3A2B73B8D500A462C7 /* Array+Extension.swift in Sources */,
 				F74B6D962A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */,
 				F711A4E32AF9310400095DD8 /* NCUtility+Image.swift in Sources */,
 				F749B652297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */,
@@ -3726,9 +3785,12 @@
 				F77ED59528C9CEA400E24ED0 /* ToolbarWidgetView.swift in Sources */,
 				F78302FB28B4C3EE00B84583 /* NCManageDatabase+Video.swift in Sources */,
 				F72EA95228B7BA2A00C88F0C /* DashboardWidgetProvider.swift in Sources */,
+				F7327E292B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */,
 				F343A4BC2A1E734600DDA874 /* Optional+Extension.swift in Sources */,
+				F7327E362B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */,
 				F783031228B4C8EC00B84583 /* CCUtility.m in Sources */,
 				F72EA95828B7BC4F00C88F0C /* FilesData.swift in Sources */,
+				F7327E312B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */,
 				F793E59E28B763C2005E4B02 /* NCAskAuthorization.swift in Sources */,
 				F78302FF28B4C45000B84583 /* NCUtilityFileSystem.swift in Sources */,
 				F73EF7B82B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */,
@@ -3759,10 +3821,13 @@
 				F702F2D125EE5B5C008F8E80 /* NCGlobal.swift in Sources */,
 				F711A4E02AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */,
 				F76D364828A4F8BF00214537 /* NCActivityIndicator.swift in Sources */,
+				F7327E242B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */,
 				F7817D0229802D7700FFBC65 /* NCViewCertificateDetails.swift in Sources */,
 				F7434B3820E2400600417916 /* NCBrand.swift in Sources */,
+				F7327E332B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */,
 				F785EE9E2461A09900B3F945 /* NCNetworking.swift in Sources */,
 				F749B655297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */,
+				F7327E382B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */,
 				F771E3D320E2392D00AFB62D /* FileProviderExtension.swift in Sources */,
 				F771E3D520E2392D00AFB62D /* FileProviderItem.swift in Sources */,
 				AF4BF616275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */,
@@ -3770,6 +3835,7 @@
 				F7434B3620E23FE000417916 /* NCManageDatabase.swift in Sources */,
 				F798F0E725880609000DAFFD /* UIColor+Extension.swift in Sources */,
 				F74B6D992A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */,
+				F7327E2C2B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */,
 				F7D68FCF28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */,
 				F73EF7CB2B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */,
 				F73EF7C32B02250B0087E6E9 /* NCManageDatabase+GPS.swift in Sources */,
@@ -3777,6 +3843,7 @@
 				F359D86B2A7D03420023F405 /* NCUtility+Exif.swift in Sources */,
 				F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
 				F7183BD72AEBDCD8000CD020 /* NCKeychain.swift in Sources */,
+				F7327E402B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */,
 				AF4BF61B27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */,
 				F70460542499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */,
 				F78A10C329322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */,
@@ -3785,6 +3852,7 @@
 				F785EEA42461A4A600B3F945 /* NCUtility.swift in Sources */,
 				F79B646226CA661600838ACA /* UIControl+Extension.swift in Sources */,
 				F73EF7AB2B0223900087E6E9 /* NCManageDatabase+Comments.swift in Sources */,
+				F7327E392B73B8D400A462C7 /* Array+Extension.swift in Sources */,
 				F78E2D6929AF02DB0024D4F3 /* Database.swift in Sources */,
 				F749B64E297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */,
 				F73EF7B32B0224350087E6E9 /* NCManageDatabase+DirectEditing.swift in Sources */,
@@ -3793,6 +3861,7 @@
 				F7A0D1372591FBC5008F8A13 /* String+Extension.swift in Sources */,
 				F771E3D720E2392D00AFB62D /* FileProviderEnumerator.swift in Sources */,
 				F74AF3A6247FB6AE00AC767B /* NCUtilityFileSystem.swift in Sources */,
+				F7B769AC2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */,
 				F73EF7E32B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */,
 				F73EF7BB2B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */,
 				F7817CFC29801A3500FFBC65 /* Data+Extension.swift in Sources */,
@@ -3828,11 +3897,12 @@
 				F70D87D025EE6E58008CBBBD /* NCRenameFile.swift in Sources */,
 				F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */,
 				F73EF7D72B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */,
-				F790110E21415BF600D7B136 /* NCViewerRichdocument.swift in Sources */,
+				F790110E21415BF600D7B136 /* NCViewerRichDocument.swift in Sources */,
 				F78ACD4021903CC20088454D /* NCGridCell.swift in Sources */,
 				F761856B29E98543006EB3B0 /* NCIntroViewController.swift in Sources */,
 				F75B0ABD244C4DBB00E58DCA /* NCActionCenter.swift in Sources */,
 				AF935067276B84E700BD078F /* NCMenu+FloatingPanel.swift in Sources */,
+				F321DA8A2B71205A00DDA0E6 /* NCTrashSelectTabBar.swift in Sources */,
 				F702F2CD25EE5B4F008F8E80 /* AppDelegate.swift in Sources */,
 				F769454022E9F077000A798A /* NCSharePaging.swift in Sources */,
 				F7EE66AD2A20B226009AE765 /* UILabel+Extension.swift in Sources */,
@@ -3847,9 +3917,11 @@
 				F73B422C2476764F00A30FD3 /* NCNotification.swift in Sources */,
 				371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */,
 				F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */,
+				F7B769A82B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */,
 				F79EDAA326B004980007D134 /* NCPlayerToolBar.swift in Sources */,
 				F77444F8222816D5000D5EB0 /* NCPickerViewController.swift in Sources */,
 				F77BB74A2899857B0090FC19 /* UINavigationController+Extension.swift in Sources */,
+				F7327E282B73A53400A462C7 /* NCNetworking+Upload.swift in Sources */,
 				F769454622E9F1B0000A798A /* NCShareCommon.swift in Sources */,
 				F70753F12542A9A200972D44 /* NCViewerMedia.swift in Sources */,
 				F76B649C2ADFFAED00014640 /* NCImageCache.swift in Sources */,
@@ -3870,11 +3942,13 @@
 				F72944F52A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */,
 				F710D2022405826100A6033D /* NCViewer+Menu.swift in Sources */,
 				F765E9CD295C585800A09ED8 /* NCUploadScanDocument.swift in Sources */,
+				F741C2242B6B9FD600E849BB /* NCMediaSelectTabBar.swift in Sources */,
 				F77A697D250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift in Sources */,
 				F7BF9D822934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */,
 				AF7E504E27A2D8FF00B5E4AF /* UIBarButton+Extension.swift in Sources */,
 				F78A10BF29322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */,
 				F704B5E92430C0B800632F5F /* NCCreateFormUploadConflictCell.swift in Sources */,
+				F7327E3D2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */,
 				F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */,
 				AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */,
 				F77BB746289984CA0090FC19 /* UIViewController+Extension.swift in Sources */,
@@ -3936,7 +4010,6 @@
 				F7381EE1218218C9000B1560 /* NCOffline.swift in Sources */,
 				F719D9E2288D396100762E33 /* NCColorPicker.swift in Sources */,
 				F73EF7DF2B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */,
-				F78071091EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m in Sources */,
 				F79B646026CA661600838ACA /* UIControl+Extension.swift in Sources */,
 				F7CA212D25F1333300826ABB /* NCAccountRequest.swift in Sources */,
 				F765F73125237E3F00391DBE /* NCRecent.swift in Sources */,
@@ -3962,7 +4035,6 @@
 				AF3FDCC22796ECC300710F60 /* NCTrash+CollectionView.swift in Sources */,
 				F70D7C3725FFBF82002B9E34 /* NCCollectionViewCommon.swift in Sources */,
 				F76D364628A4F8BF00214537 /* NCActivityIndicator.swift in Sources */,
-				F7020FCE2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift in Sources */,
 				F7134186259747BA00768D21 /* NCPushNotification.m in Sources */,
 				F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */,
 				F726EEEC1FED1C820030B9C8 /* NCEndToEndInitialize.swift in Sources */,
@@ -3982,6 +4054,7 @@
 				F7AE00F5230D5F9E007ACF8A /* NCLoginWeb.swift in Sources */,
 				F707C26521A2DC5200F6181E /* NCStoreReview.swift in Sources */,
 				F7BAADCB1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */,
+				F737DA9D2B7B893C0063BAFC /* NCPasscode.swift in Sources */,
 				F77C97392953131000FDDD09 /* NCCameraRoll.swift in Sources */,
 				F343A4B32A1E01FF00DDA874 /* PHAsset+Extension.swift in Sources */,
 				F70968A424212C4E00ED60E5 /* NCLivePhoto.swift in Sources */,
@@ -3989,7 +4062,6 @@
 				F7BC288026663F85004D46C5 /* NCViewCertificateDetails.swift in Sources */,
 				F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnaill.swift in Sources */,
 				F73EF7AF2B0224350087E6E9 /* NCManageDatabase+DirectEditing.swift in Sources */,
-				F702F2E625EE5C86008F8E80 /* NCAudioRecorderViewController.swift in Sources */,
 				D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */,
 				F745B253222D88AE00346520 /* NCLoginQRCode.swift in Sources */,
 				F7CBC31C24F78E79004D3812 /* NCSortMenu.swift in Sources */,
@@ -4001,6 +4073,8 @@
 				F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */,
 				F704B5E52430AA8000632F5F /* NCCreateFormUploadConflict.swift in Sources */,
 				F765608F23BF813600765969 /* NCContentPresenter.swift in Sources */,
+				F7327E352B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */,
+				F76687072B7D067400779E3F /* NCAudioRecorderViewController.swift in Sources */,
 				F343A4BB2A1E734600DDA874 /* Optional+Extension.swift in Sources */,
 				F7D68FCC28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */,
 				F78E2D6529AF02DB0024D4F3 /* Database.swift in Sources */,
@@ -4017,11 +4091,14 @@
 				F7581D1A25EFDA61004DC699 /* NCLoginWeb+Menu.swift in Sources */,
 				F7864ACC2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
 				F7F4F11027ECDC4A008676F9 /* UIDevice+Extension.swift in Sources */,
+				F7327E302B73A86700A462C7 /* NCNetworking+WebDAV.swift in Sources */,
 				F77B0ED11D118A16002130FE /* Acknowledgements.m in Sources */,
 				F79FFB262A97C24A0055EEA4 /* NCNetworkingE2EEMarkFolder.swift in Sources */,
-				F70D8D8124A4A9BF000A5756 /* NCNetworkingProcessUpload.swift in Sources */,
+				F70D8D8124A4A9BF000A5756 /* NCNetworkingProcess.swift in Sources */,
 				F7D96FCC246ED7E200536D73 /* NCNetworkingCheckRemoteUser.swift in Sources */,
+				F38F71252B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift in Sources */,
 				F7E4D9C422ED929B003675FD /* NCShareCommentsCell.swift in Sources */,
+				F7327E202B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */,
 				F717402E24F699A5000C87D5 /* NCFavorite.swift in Sources */,
 				AF2D7C7E2742559100ADF566 /* NCShareUserCell.swift in Sources */,
 				AF7E505027A2D92300B5E4AF /* NCSelectableNavigationView.swift in Sources */,
@@ -4073,6 +4150,7 @@
 				F7A8D74128F18254008BBE1C /* UIColor+Extension.swift in Sources */,
 				F73EF7D92B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */,
 				F7864ACE2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */,
+				F7B769AA2B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift in Sources */,
 				F7A8D74028F18212008BBE1C /* UIImage+Extension.swift in Sources */,
 				F73EF7D12B0225BA0087E6E9 /* NCManageDatabase+Tag.swift in Sources */,
 				F73EF7C92B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */,
@@ -4917,7 +4995,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0;
+				CURRENT_PROJECT_VERSION = 7;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4943,7 +5021,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 5.0.1;
+				MARKETING_VERSION = 5.1.0;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -4982,7 +5060,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 0;
+				CURRENT_PROJECT_VERSION = 7;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
@@ -5005,7 +5083,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 5.0.1;
+				MARKETING_VERSION = 5.1.0;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -5297,7 +5375,7 @@
 			repositoryURL = "https://github.com/nextcloud/NextcloudKit";
 			requirement = {
 				kind = exactVersion;
-				version = 2.9.5;
+				version = 2.9.6;
 			};
 		};
 		F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {

+ 36 - 8
Share/NCShareExtension.swift

@@ -26,6 +26,7 @@
 import UIKit
 import NextcloudKit
 import JGProgressHUD
+import TOPasscodeViewController
 
 enum NCShareExtensionError: Error {
     case cancel, fileUpload, noAccount, noFiles
@@ -144,13 +145,12 @@ class NCShareExtension: UIViewController {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         guard serverUrl.isEmpty else { return }
-
-        guard let activeAccount = NCManageDatabase.shared.getActiveAccount() else {
+        guard let activeAccount = NCManageDatabase.shared.getActiveAccount(),
+              !NCPasscode.shared.isPasscodeReset else {
             return showAlert(description: "_no_active_account_") {
                 self.cancel(with: .noAccount)
             }
         }
-
         accountRequestChangeAccount(account: activeAccount.account)
         guard let inputItems = extensionContext?.inputItems as? [NSExtensionItem] else {
             cancel(with: .noFiles)
@@ -160,6 +160,9 @@ class NCShareExtension: UIViewController {
             self.filesName = fileNames
             DispatchQueue.main.async { self.setCommandView() }
         }
+        NCPasscode.shared.presentPasscode(viewController: self, delegate: self) {
+            NCPasscode.shared.enableTouchFaceID()
+        }
     }
 
     override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
@@ -309,6 +312,7 @@ extension NCShareExtension {
             metadata.sessionSelector = NCGlobal.shared.selectorUploadFileShareExtension
             metadata.size = utilityFileSystem.getFileSize(filePath: toPath)
             metadata.status = NCGlobal.shared.metadataStatusWaitUpload
+            metadata.sessionDate = Date()
             if NCManageDatabase.shared.getMetadataConflict(account: activeAccount.account, serverUrl: serverUrl, fileNameView: fileName) != nil {
                 conflicts.append(metadata)
             } else {
@@ -349,14 +353,20 @@ extension NCShareExtension {
         // E2EE
         metadata.e2eEncrypted = metadata.isDirectoryE2EE
 
-        hud.textLabel.text = NSLocalizedString("_upload_file_", comment: "") + " \(counterUploaded + 1) " + NSLocalizedString("_of_", comment: "") + " \(filesName.count)"
-        hud.show(in: self.view)
+        DispatchQueue.main.async {
+            self.hud.show(in: self.view)
+            self.hud.textLabel.text = NSLocalizedString("_upload_file_", comment: "") + " \(self.counterUploaded + 1) " + NSLocalizedString("_of_", comment: "") + " \(self.filesName.count)"
+        }
 
         NCNetworking.shared.upload(metadata: metadata, uploadE2EEDelegate: self, hudView: self.view, hud: JGProgressHUD()) {
-            self.hud.progress = 0
+            DispatchQueue.main.async {
+                self.hud.progress = 0
+            }
         } progressHandler: { _, _, fractionCompleted in
-            self.hud.progress = Float(fractionCompleted)
-        } completion: { error in
+            DispatchQueue.main.async {
+                self.hud.progress = Float(fractionCompleted)
+            }
+        } completion: { _, error in
             if error != .success {
                 NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
                 self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
@@ -394,3 +404,21 @@ extension NCShareExtension: uploadE2EEDelegate {
         self.hud.progress = Float(fractionCompleted)
     }
 }
+
+extension NCShareExtension: NCPasscodeDelegate {
+    func passcodeReset(_ passcodeViewController: TOPasscodeViewController) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            passcodeViewController.dismiss(animated: false)
+            self.cancel(with: .noAccount)
+        }
+    }
+
+    func evaluatePolicy(_ passcodeViewController: TOPasscodeViewController, isCorrectCode: Bool) {
+        if !isCorrectCode {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+                passcodeViewController.dismiss(animated: false)
+                self.cancel(with: .noAccount)
+            }
+        }
+    }
+}

+ 103 - 298
iOSClient/AppDelegate.swift

@@ -31,7 +31,7 @@ import WidgetKit
 import Queuer
 
 @UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, TOPasscodeViewControllerDelegate, NCAccountRequestDelegate, NCViewCertificateDetailsDelegate, NCUserBaseUrl {
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, NCUserBaseUrl {
 
     var backgroundSessionCompletionHandler: (() -> Void)?
     var window: UIWindow?
@@ -53,9 +53,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     var disableSharesView: Bool = false
     var documentPickerViewController: NCDocumentPickerViewController?
     var timerErrorNetworking: Timer?
-    private var privacyProtectionWindow: UIWindow?
     var isAppRefresh: Bool = false
-    var isAppProcessing: Bool = false
+    var isProcessingTask: Bool = false
 
     var isUiTestingEnabled: Bool {
         return ProcessInfo.processInfo.arguments.contains("UI_TESTING")
@@ -166,10 +165,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
                     window?.makeKeyAndVisible()
                 }
             }
-        }
-
-        self.presentPasscode {
-            self.enableTouchFaceID()
+        } else {
+            NCPasscode.shared.presentPasscode(delegate: self) {
+                NCPasscode.shared.enableTouchFaceID()
+            }
         }
 
         return true
@@ -188,11 +187,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0.5)
 
         // START OBSERVE/TIMER UPLOAD PROCESS
-        NCNetworkingProcessUpload.shared.observeTableMetadata()
-        NCNetworkingProcessUpload.shared.startTimer()
+        NCNetworkingProcess.shared.observeTableMetadata()
+        NCNetworkingProcess.shared.startTimer()
 
         if !NCAskAuthorization().isRequesting {
-            hidePrivacyProtectionWindow()
+            NCPasscode.shared.hidePrivacyProtectionWindow()
         }
 
         NCService().startRequestServicesServer()
@@ -212,11 +211,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         guard !account.isEmpty else { return }
 
         // STOP OBSERVE/TIMER UPLOAD PROCESS
-        NCNetworkingProcessUpload.shared.invalidateObserveTableMetadata()
-        NCNetworkingProcessUpload.shared.stopTimer()
+        NCNetworkingProcess.shared.invalidateObserveTableMetadata()
+        NCNetworkingProcess.shared.stopTimer()
 
         if NCKeychain().privacyScreenEnabled {
-            showPrivacyProtectionWindow()
+            NCPasscode.shared.showPrivacyProtectionWindow()
         }
 
         // Reload Widget
@@ -237,7 +236,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
         guard !account.isEmpty else { return }
 
-        enableTouchFaceID()
+        NCPasscode.shared.enableTouchFaceID()
 
         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationWillEnterForeground)
         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus)
@@ -270,9 +269,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         scheduleAppRefresh()
         scheduleAppProcessing()
         NCNetworking.shared.cancelAllQueue()
-        NCNetworking.shared.cancelDataTask()
         NCNetworking.shared.cancelDownloadTasks()
-        presentPasscode { }
+        NCNetworking.shared.cancelUploadTasks()
+        NCPasscode.shared.presentPasscode(delegate: self) { }
 
         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationDidEnterBackground)
     }
@@ -332,20 +331,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     func handleAppRefresh(_ task: BGTask) {
         scheduleAppRefresh()
 
-        if isAppProcessing {
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Processing task already in progress, abort.")
-            task.setTaskCompleted(success: true)
-            return
+        if isProcessingTask {
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] ProcessingTask already in progress, abort.")
+            return task.setTaskCompleted(success: true)
         }
         isAppRefresh = true
 
-        NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task auto upload with \(items) uploads")
-            NCNetworkingProcessUpload.shared.start { items in
-                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task upload process with \(items) uploads")
-                task.setTaskCompleted(success: true)
-                self.isAppRefresh = false
-            }
+        handleAppRefreshProcessingTask(taskText: "AppRefresh") {
+            task.setTaskCompleted(success: true)
+            self.isAppRefresh = false
         }
     }
 
@@ -353,18 +347,44 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         scheduleAppProcessing()
 
         if isAppRefresh {
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task already in progress, abort.")
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] AppRefresh already in progress, abort.")
+            return task.setTaskCompleted(success: true)
+        }
+        isProcessingTask = true
+
+        handleAppRefreshProcessingTask(taskText: "ProcessingTask") {
             task.setTaskCompleted(success: true)
-            return
+            self.isProcessingTask = false
         }
-        isAppProcessing = true
+    }
+
+    func handleAppRefreshProcessingTask(taskText: String, completion: @escaping () -> Void = {}) {
+        let semaphore = DispatchSemaphore(value: 0)
 
         NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Processing task auto upload with \(items) uploads")
-            NCNetworkingProcessUpload.shared.start { items in
-                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Processing task upload process with \(items) uploads")
-                task.setTaskCompleted(success: true)
-                self.isAppProcessing = false
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) auto upload with \(items) uploads")
+
+            NCNetworkingProcess.shared.start { counterDownload, counterUpload in
+                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) networking process with download: \(counterDownload) upload: \(counterUpload)")
+
+                if taskText == "ProcessingTask",
+                   items == 0, counterDownload == 0, counterUpload == 0,
+                   let directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "account == %@ AND offline == true", self.account), sorted: "offlineDate", ascending: true) {
+
+                    for directory: tableDirectory in directories {
+                        // only 3 time for day
+                        if let offlineDate = directory.offlineDate, offlineDate.addingTimeInterval(28800) > Date() {
+                            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) skip synchronization for \(directory.serverUrl) in date \(offlineDate)")
+                            continue
+                        }
+                        NCNetworking.shared.synchronization(account: self.account, serverUrl: directory.serverUrl, add: false) { errorCode, items in
+                            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) end synchronization for \(directory.serverUrl), errorCode: \(errorCode), item: \(items)")
+                            semaphore.signal()
+                        }
+                        semaphore.wait()
+                    }
+                }
+                completion()
             }
         }
     }
@@ -410,7 +430,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     }
 
     func nextcloudPushNotificationAction(data: [String: AnyObject]) {
-
         guard let data = NCApplicationHandle().nextcloudPushNotificationAction(data: data) else { return }
         var findAccount: Bool = false
 
@@ -569,10 +588,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         window?.rootViewController?.present(alertController, animated: true)
     }
 
-    func viewCertificateDetailsDismiss(host: String) {
-        trustCertificateError(host: host)
-    }
-
     // MARK: - Account
 
     @objc func changeAccount(_ account: String, userProfile: NKUserProfile?) {
@@ -669,223 +684,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         return NKShareAccounts().putShareAccounts(at: dirGroupApps, app: NCGlobal.shared.appScheme, dataAccounts: accounts)
     }
 
-    // MARK: - Account Request
-
-    func accountRequestChangeAccount(account: String) {
-        changeAccount(account, userProfile: nil)
-    }
-
-    func requestAccount() {
-
-        if isPasscodePresented { return }
-        if !NCKeychain().accountRequest { return }
-
-        let accounts = NCManageDatabase.shared.getAllAccount()
-
-        if accounts.count > 1 {
-
-            if let vcAccountRequest = UIStoryboard(name: "NCAccountRequest", bundle: nil).instantiateInitialViewController() as? NCAccountRequest {
-
-                vcAccountRequest.activeAccount = NCManageDatabase.shared.getActiveAccount()
-                vcAccountRequest.accounts = accounts
-                vcAccountRequest.enableTimerProgress = true
-                vcAccountRequest.enableAddAccount = false
-                vcAccountRequest.dismissDidEnterBackground = false
-                vcAccountRequest.delegate = self
-
-                let screenHeighMax = UIScreen.main.bounds.height - (UIScreen.main.bounds.height / 5)
-                let numberCell = accounts.count
-                let height = min(CGFloat(numberCell * Int(vcAccountRequest.heightCell) + 45), screenHeighMax)
-
-                let popup = NCPopupViewController(contentController: vcAccountRequest, popupWidth: 300, popupHeight: height + 20)
-                popup.backgroundAlpha = 0.8
-
-                window?.rootViewController?.present(popup, animated: true)
-
-                vcAccountRequest.startTimer()
-            }
-        }
-    }
-
-    // MARK: - Passcode
-
-    var isPasscodeReset: Bool {
-        let passcodeCounterFailReset = NCKeychain().passcodeCounterFailReset
-        return NCKeychain().resetAppCounterFail && passcodeCounterFailReset >= NCBrandOptions.shared.resetAppPasscodeAttempts
-    }
-
-    var isPasscodeFail: Bool {
-        let passcodeCounterFail = NCKeychain().passcodeCounterFail
-        return passcodeCounterFail > 0 && passcodeCounterFail.isMultiple(of: 3)
-    }
-
-    var isPasscodePresented: Bool {
-        return privacyProtectionWindow?.rootViewController?.presentedViewController is TOPasscodeViewController
-    }
-
-    func presentPasscode(completion: @escaping () -> Void) {
-
-        var error: NSError?
-        defer { self.requestAccount() }
-
-        let presentedViewController = window?.rootViewController?.presentedViewController
-        guard !account.isEmpty, NCKeychain().passcode != nil, NCKeychain().requestPasscodeAtStart, !(presentedViewController is NCLoginNavigationController) else { return }
-
-        // Make sure we have a privacy window (in case it's not enabled)
-        showPrivacyProtectionWindow()
-
-        let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: false)
-        passcodeViewController.delegate = self
-        passcodeViewController.keypadButtonShowLettering = false
-        if NCKeychain().touchFaceID, LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
-            if error == nil {
-                if LAContext().biometryType == .faceID {
-                    passcodeViewController.biometryType = .faceID
-                } else if LAContext().biometryType == .touchID {
-                    passcodeViewController.biometryType = .touchID
-                }
-                passcodeViewController.allowBiometricValidation = true
-                passcodeViewController.automaticallyPromptForBiometricValidation = false
-            }
-        }
-
-        // show passcode on top of privacy window
-        privacyProtectionWindow?.rootViewController?.present(passcodeViewController, animated: true, completion: {
-            self.openAlert(passcodeViewController: passcodeViewController)
-            completion()
-        })
-    }
-
-    func enableTouchFaceID() {
-        guard !account.isEmpty,
-              NCKeychain().touchFaceID,
-              NCKeychain().passcode != nil,
-              NCKeychain().requestPasscodeAtStart,
-              !isPasscodeFail,
-              !isPasscodeReset,
-              let passcodeViewController = privacyProtectionWindow?.rootViewController?.presentedViewController as? TOPasscodeViewController
-        else { return }
-
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
-
-            LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, evaluateError in
-                if success {
-                    DispatchQueue.main.async {
-                        passcodeViewController.dismiss(animated: true) {
-                            NCKeychain().passcodeCounterFail = 0
-                            NCKeychain().passcodeCounterFailReset = 0
-                            self.hidePrivacyProtectionWindow()
-                            self.requestAccount()
-                        }
-                    }
-                } else {
-                    if let error = evaluateError {
-                        switch error._code {
-                        case LAError.userFallback.rawValue, LAError.authenticationFailed.rawValue:
-                            if LAContext().biometryType == .faceID {
-                                NCKeychain().passcodeCounterFail = 2
-                                NCKeychain().passcodeCounterFailReset += 2
-                            } else {
-                                NCKeychain().passcodeCounterFail = 3
-                                NCKeychain().passcodeCounterFailReset += 3
-                            }
-                            self.openAlert(passcodeViewController: passcodeViewController)
-                        case LAError.biometryLockout.rawValue:
-                            LAContext().evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: NSLocalizedString("_deviceOwnerAuthentication_", comment: ""), reply: { success, _ in
-                                if success {
-                                    DispatchQueue.main.async {
-                                        NCKeychain().passcodeCounterFail = 0
-                                        self.enableTouchFaceID()
-                                    }
-                                }
-                            })
-                        case LAError.userCancel.rawValue:
-                            NCKeychain().passcodeCounterFail += 1
-                            NCKeychain().passcodeCounterFailReset += 1
-                        default:
-                            break
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    func didInputCorrectPasscode(in passcodeViewController: TOPasscodeViewController) {
-        DispatchQueue.main.async {
-            passcodeViewController.dismiss(animated: true) {
-                NCKeychain().passcodeCounterFail = 0
-                NCKeychain().passcodeCounterFailReset = 0
-                self.hidePrivacyProtectionWindow()
-                self.requestAccount()
-            }
-        }
-    }
-
-    func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool {
-        if code == NCKeychain().passcode {
-            return true
-        } else {
-            NCKeychain().passcodeCounterFail += 1
-            NCKeychain().passcodeCounterFailReset += 1
-            openAlert(passcodeViewController: passcodeViewController)
-            return false
-        }
-    }
-
-    func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) {
-        enableTouchFaceID()
-    }
-
-    func openAlert(passcodeViewController: TOPasscodeViewController) {
-
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-
-            if self.isPasscodeReset {
-
-                passcodeViewController.setContentHidden(true, animated: true)
-
-                let alertController = UIAlertController(title: NSLocalizedString("_reset_wrong_passcode_", comment: ""), message: nil, preferredStyle: .alert)
-                passcodeViewController.present(alertController, animated: true, completion: { })
-
-                DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
-                    self.resetApplication()
-                }
-
-            } else if self.isPasscodeFail {
-
-                passcodeViewController.setContentHidden(true, animated: true)
-
-                let alertController = UIAlertController(title: NSLocalizedString("_passcode_counter_fail_", comment: ""), message: nil, preferredStyle: .alert)
-                passcodeViewController.present(alertController, animated: true, completion: { })
-
-                var seconds = NCBrandOptions.shared.passcodeSecondsFail
-                _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
-                    alertController.message = "\(seconds) " + NSLocalizedString("_seconds_", comment: "")
-                    seconds -= 1
-                    if seconds < 0 {
-                        timer.invalidate()
-                        alertController.dismiss(animated: true)
-                        passcodeViewController.setContentHidden(false, animated: true)
-                        NCKeychain().passcodeCounterFail = 0
-                        self.enableTouchFaceID()
-                    }
-                }
-            }
-        }
-    }
-
     // MARK: - Reset Application
 
     @objc func resetApplication() {
 
         let utilityFileSystem = NCUtilityFileSystem()
 
-        NCNetworking.shared.cancelAllQueue()
-        NCNetworking.shared.cancelDataTask()
-        NCNetworking.shared.cancelDownloadTasks()
-        NCNetworking.shared.cancelUploadTasks()
-        NCNetworking.shared.cancelUploadBackgroundTask(withNotification: false)
+        NCNetworking.shared.cancelAllTask()
 
         URLCache.shared.memoryCapacity = 0
         URLCache.shared.diskCapacity = 0
@@ -899,35 +704,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         exit(0)
     }
 
-    // MARK: - Privacy Protection
-
-    private func showPrivacyProtectionWindow() {
-        guard privacyProtectionWindow == nil else {
-            privacyProtectionWindow?.isHidden = false
-            return
-        }
-
-        privacyProtectionWindow = UIWindow(frame: UIScreen.main.bounds)
-
-        let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
-        let initialViewController = storyboard.instantiateInitialViewController()
-
-        self.privacyProtectionWindow?.rootViewController = initialViewController
-
-        privacyProtectionWindow?.windowLevel = .alert + 1
-        privacyProtectionWindow?.makeKeyAndVisible()
-    }
-
-    func hidePrivacyProtectionWindow() {
-        guard !(privacyProtectionWindow?.rootViewController?.presentedViewController is TOPasscodeViewController) else { return }
-        UIWindow.animate(withDuration: 0.25) {
-            self.privacyProtectionWindow?.alpha = 0
-        } completion: { _ in
-            self.privacyProtectionWindow?.isHidden = true
-            self.privacyProtectionWindow = nil
-        }
-    }
-
     // MARK: - Universal Links
 
     func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
@@ -1001,14 +777,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
                     NCAskAuthorization().askAuthorizationAudioRecord(viewController: rootViewController) { hasPermission in
                         if hasPermission {
-                            let fileName = NCUtilityFileSystem().createFileNameDate(NSLocalizedString("_voice_memo_filename_", comment: ""), ext: "m4a")
                             if let viewController = UIStoryboard(name: "NCAudioRecorderViewController", bundle: nil).instantiateInitialViewController() as? NCAudioRecorderViewController {
-
-                                viewController.delegate = self
-                                viewController.createRecorder(fileName: fileName)
                                 viewController.modalTransitionStyle = .crossDissolve
                                 viewController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
-
                                 rootViewController.present(viewController, animated: true, completion: nil)
                             }
                         }
@@ -1108,25 +879,59 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     }
 }
 
-// MARK: - NCAudioRecorder ViewController Delegate
-
-extension AppDelegate: NCAudioRecorderViewControllerDelegate {
-
-    func didFinishRecording(_ viewController: NCAudioRecorderViewController, fileName: String) {
+// MARK: -
 
-        guard let navigationController = UIStoryboard(name: "NCCreateFormUploadVoiceNote", bundle: nil).instantiateInitialViewController() as? UINavigationController,
-              let viewController = navigationController.topViewController as? NCCreateFormUploadVoiceNote else { return }
-        navigationController.modalPresentationStyle = .formSheet
-        viewController.setup(serverUrl: activeServerUrl, fileNamePath: NSTemporaryDirectory() + fileName, fileName: fileName)
-        window?.rootViewController?.present(navigationController, animated: true)
+extension AppDelegate: NCViewCertificateDetailsDelegate {
+    func viewCertificateDetailsDismiss(host: String) {
+        trustCertificateError(host: host)
     }
-
-    func didFinishWithoutRecording(_ viewController: NCAudioRecorderViewController, fileName: String) { }
 }
 
 extension AppDelegate: NCCreateFormUploadConflictDelegate {
     func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) {
         guard let metadatas = metadatas, !metadatas.isEmpty else { return }
-        NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: metadatas) { _ in }
+        NCNetworkingProcess.shared.createProcessUploads(metadatas: metadatas)
+    }
+}
+
+extension AppDelegate: NCPasscodeDelegate {
+    func requestedAccount() {
+        guard !NCPasscode.shared.isPasscodePresented, NCKeychain().accountRequest else {
+            return
+        }
+
+        let accounts = NCManageDatabase.shared.getAllAccount()
+        if accounts.count > 1 {
+
+            if let viewController = UIStoryboard(name: "NCAccountRequest", bundle: nil).instantiateInitialViewController() as? NCAccountRequest {
+
+                viewController.activeAccount = NCManageDatabase.shared.getActiveAccount()
+                viewController.accounts = accounts
+                viewController.enableTimerProgress = true
+                viewController.enableAddAccount = false
+                viewController.dismissDidEnterBackground = false
+                viewController.delegate = self
+
+                let screenHeighMax = UIScreen.main.bounds.height - (UIScreen.main.bounds.height / 5)
+                let numberCell = accounts.count
+                let height = min(CGFloat(numberCell * Int(viewController.heightCell) + 45), screenHeighMax)
+
+                let popup = NCPopupViewController(contentController: viewController, popupWidth: 300, popupHeight: height + 20)
+                popup.backgroundAlpha = 0.8
+
+                window?.rootViewController?.present(popup, animated: true)
+                viewController.startTimer()
+            }
+        }
+    }
+
+    func passcodeReset(_ passcodeViewController: TOPasscodeViewController) {
+        resetApplication()
+    }
+}
+
+extension AppDelegate: NCAccountRequestDelegate {
+    func accountRequestChangeAccount(account: String) {
+        changeAccount(account, userProfile: nil)
     }
 }

+ 8 - 10
iOSClient/Main/AudioRecorder/NCAudioRecorderViewController.storyboard → iOSClient/AudioRecorder/NCAudioRecorderViewController.storyboard

@@ -1,11 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="9IE-bj-VJb">
-    <device id="retina4_7" orientation="portrait">
-        <adaptation id="fullscreen"/>
-    </device>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="9IE-bj-VJb">
+    <device id="retina4_7" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <scenes>
@@ -21,8 +19,8 @@
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
-                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tRu-33-Q2b" userLabel="buttonView">
-                                <rect key="frame" x="0.0" y="20" width="375" height="647"/>
+                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tRu-33-Q2b" userLabel="buttonView">
+                                <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                                 <connections>
                                     <action selector="touchViewController" destination="9IE-bj-VJb" eventType="touchUpInside" id="Dex-dc-29L"/>
                                 </connections>
@@ -51,7 +49,7 @@
                                         <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                         <nil key="highlightedColor"/>
                                     </label>
-                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="bottom" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lnv-LR-qq5" userLabel="button">
+                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="bottom" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lnv-LR-qq5" userLabel="button">
                                         <rect key="frame" x="0.0" y="0.0" width="200" height="250"/>
                                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
                                         <state key="normal">
@@ -96,10 +94,10 @@
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
                         <constraints>
                             <constraint firstItem="Ztv-M0-yUI" firstAttribute="centerY" secondItem="tQN-Gk-6M1" secondAttribute="centerY" id="4ah-zH-qPv"/>
-                            <constraint firstItem="6hQ-x4-s9V" firstAttribute="top" secondItem="tRu-33-Q2b" secondAttribute="bottom" id="Iyw-Xf-NUq"/>
+                            <constraint firstAttribute="bottom" secondItem="tRu-33-Q2b" secondAttribute="bottom" id="Iyw-Xf-NUq"/>
                             <constraint firstItem="Ztv-M0-yUI" firstAttribute="centerX" secondItem="tQN-Gk-6M1" secondAttribute="centerX" id="QMu-hw-0s3"/>
                             <constraint firstItem="tRu-33-Q2b" firstAttribute="leading" secondItem="tQN-Gk-6M1" secondAttribute="leading" id="iDG-p5-AZP"/>
-                            <constraint firstItem="tRu-33-Q2b" firstAttribute="top" secondItem="fma-yb-dlL" secondAttribute="bottom" id="rco-z5-Lgs"/>
+                            <constraint firstItem="tRu-33-Q2b" firstAttribute="top" secondItem="tQN-Gk-6M1" secondAttribute="top" id="rco-z5-Lgs"/>
                             <constraint firstAttribute="trailing" secondItem="tRu-33-Q2b" secondAttribute="trailing" id="uMJ-XD-sjl"/>
                         </constraints>
                     </view>

+ 26 - 42
iOSClient/Main/AudioRecorder/NCAudioRecorderViewController.swift → iOSClient/AudioRecorder/NCAudioRecorderViewController.swift

@@ -28,17 +28,12 @@ import UIKit
 import AVFoundation
 import QuartzCore
 
-@objc protocol NCAudioRecorderViewControllerDelegate: AnyObject {
-    func didFinishRecording(_ viewController: NCAudioRecorderViewController, fileName: String)
-    func didFinishWithoutRecording(_ viewController: NCAudioRecorderViewController, fileName: String)
-}
-
 class NCAudioRecorderViewController: UIViewController, NCAudioRecorderDelegate {
 
-    open weak var delegate: NCAudioRecorderViewControllerDelegate?
     var recording: NCAudioRecorder!
     var startDate: Date = Date()
     var fileName: String = ""
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
 
     @IBOutlet weak var contentContainerView: UIView!
     @IBOutlet weak var durationLabel: UILabel!
@@ -52,11 +47,23 @@ class NCAudioRecorderViewController: UIViewController, NCAudioRecorderDelegate {
 
         voiceRecordHUD.update(0.0)
         durationLabel.text = ""
-        startStopLabel.text = NSLocalizedString("_voice_memo_start_", comment: "")
+        startStopLabel.text = NSLocalizedString("_wait_", comment: "")
 
         view.backgroundColor = .clear
         contentContainerView.backgroundColor = UIColor.lightGray
         voiceRecordHUD.fillColor = UIColor.green
+
+        Task {
+            self.fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + ".m4a", account: self.appDelegate.account, serverUrl: self.appDelegate.activeServerUrl)
+            recording = NCAudioRecorder(to: self.fileName)
+            recording.delegate = self
+            do {
+                try self.recording.prepare()
+                startStopLabel.text = NSLocalizedString("_voice_memo_start_", comment: "")
+            } catch {
+                print(error)
+            }
+        }
     }
 
     override func viewWillAppear(_ animated: Bool) {
@@ -74,29 +81,21 @@ class NCAudioRecorderViewController: UIViewController, NCAudioRecorderDelegate {
     // MARK: - Action
 
     @IBAction func touchViewController() {
-
         if recording.state == .record {
             startStop()
         } else {
-            dismiss(animated: true) {
-                self.delegate?.didFinishWithoutRecording(self, fileName: self.fileName)
-            }
+            dismiss(animated: true)
         }
     }
 
     @IBAction func startStop() {
-
         if recording.state == .record {
-
             recording.stop()
             voiceRecordHUD.update(0.0)
-
             dismiss(animated: true) {
-                self.delegate?.didFinishRecording(self, fileName: self.fileName)
+                self.uploadMetadata()
             }
-
         } else {
-
             do {
                 try recording.record()
                 startDate = Date()
@@ -107,22 +106,16 @@ class NCAudioRecorderViewController: UIViewController, NCAudioRecorderDelegate {
         }
     }
 
-    // MARK: - Code
-
-    func createRecorder(fileName: String) {
-
-        self.fileName = fileName
-        recording = NCAudioRecorder(to: fileName)
-        recording.delegate = self
-
-        DispatchQueue.global().async {
-            // Background thread
-            do {
-                try self.recording.prepare()
-            } catch {
-                print(error)
-            }
-        }
+    func uploadMetadata() {
+        let fileNamePath = NSTemporaryDirectory() + self.fileName
+        let metadata = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID().uuidString, serverUrl: appDelegate.activeServerUrl, urlBase: appDelegate.urlBase, url: "", contentType: "")
+        metadata.session = NCNetworking.shared.sessionUploadBackground
+        metadata.sessionSelector = NCGlobal.shared.selectorUploadFile
+        metadata.status = NCGlobal.shared.metadataStatusWaitUpload
+        metadata.sessionDate = Date()
+        metadata.size = NCUtilityFileSystem().getFileSize(filePath: fileNamePath)
+        NCUtilityFileSystem().copyFile(atPath: fileNamePath, toPath: NCUtilityFileSystem().getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
+        NCNetworkingProcess.shared.createProcessUploads(metadatas: [metadata])
     }
 
     func audioMeterDidUpdate(_ db: Float) {
@@ -225,11 +218,9 @@ open class NCAudioRecorder: NSObject {
     }
 
     open func record() throws {
-
         if recorder == nil {
             try prepare()
         }
-
         self.state = .record
         if self.metering {
             self.startMetering()
@@ -238,7 +229,6 @@ open class NCAudioRecorder: NSObject {
     }
 
     open func stop() {
-
         switch state {
         case .play:
             player?.stop()
@@ -250,7 +240,6 @@ open class NCAudioRecorder: NSObject {
         default:
             break
         }
-
         state = .none
     }
 
@@ -258,16 +247,12 @@ open class NCAudioRecorder: NSObject {
 
     @objc func updateMeter() {
         guard let recorder = recorder else { return }
-
         recorder.updateMeters()
-
         let dB = recorder.averagePower(forChannel: 0)
-
         delegate?.audioMeterDidUpdate?(dB)
     }
 
     fileprivate func startMetering() {
-
         link = CADisplayLink(target: self, selector: #selector(NCAudioRecorder.updateMeter))
         link?.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
     }
@@ -281,7 +266,6 @@ open class NCAudioRecorder: NSObject {
 @IBDesignable
 class VoiceRecordHUD: UIView {
     @IBInspectable var rate: CGFloat = 0.0
-
     @IBInspectable var fillColor: UIColor = UIColor.green {
         didSet {
             setNeedsDisplay()

+ 2 - 2
iOSClient/Data/NCManageDatabase+Capabilities.swift

@@ -294,9 +294,9 @@ extension NCManageDatabase {
         guard let jsonData = jsonData else { return }
 
         do {
-            var global = NCGlobal.shared
+            let global = NCGlobal.shared
             let json = try JSONDecoder().decode(CapabilityNextcloud.self, from: jsonData)
-            var data = json.ocs.data
+            let data = json.ocs.data
 
             global.capabilityServerVersion = data.version.string
             global.capabilityServerVersionMajor = data.version.major

+ 13 - 12
iOSClient/Data/NCManageDatabase+Directory.swift

@@ -35,6 +35,7 @@ class tableDirectory: Object {
     @objc dynamic var fileId = ""
     @objc dynamic var ocId = ""
     @objc dynamic var offline: Bool = false
+    @objc dynamic var offlineDate: Date?
     @objc dynamic var permissions = ""
     @objc dynamic var richWorkspace: String?
     @objc dynamic var serverUrl = ""
@@ -47,7 +48,6 @@ class tableDirectory: Object {
 extension NCManageDatabase {
 
     func addDirectory(encrypted: Bool, favorite: Bool, ocId: String, fileId: String, etag: String? = nil, permissions: String? = nil, serverUrl: String, account: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -101,7 +101,6 @@ extension NCManageDatabase {
     }
 
     func setDirectory(serverUrl: String, serverUrlTo: String? = nil, etag: String? = nil, ocId: String? = nil, fileId: String? = nil, encrypted: Bool, richWorkspace: String? = nil, account: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -132,7 +131,6 @@ extension NCManageDatabase {
     }
 
     func cleanEtagDirectory(account: String, serverUrl: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -146,7 +144,6 @@ extension NCManageDatabase {
     }
 
     func getTableDirectory(predicate: NSPredicate) -> tableDirectory? {
-
         do {
             let realm = try Realm()
             realm.refresh()
@@ -155,24 +152,20 @@ extension NCManageDatabase {
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
         }
-
         return nil
     }
 
     func getTableDirectory(account: String, serverUrl: String) -> tableDirectory? {
-
         do {
             let realm = try Realm()
             return realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
         }
-
         return nil
     }
 
     func getTableDirectory(ocId: String) -> tableDirectory? {
-
         do {
             let realm = try Realm()
             realm.refresh()
@@ -184,7 +177,6 @@ extension NCManageDatabase {
     }
 
     func getTablesDirectory(predicate: NSPredicate, sorted: String, ascending: Bool) -> [tableDirectory]? {
-
         do {
             let realm = try Realm()
             realm.refresh()
@@ -202,7 +194,6 @@ extension NCManageDatabase {
     }
 
     func renameDirectory(ocId: String, serverUrl: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -215,7 +206,6 @@ extension NCManageDatabase {
     }
 
     func setDirectory(serverUrl: String, offline: Bool, account: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -227,9 +217,20 @@ extension NCManageDatabase {
         }
     }
 
+    func setDirectorySynchronizationDate(serverUrl: String, account: String) {
+        do {
+            let realm = try Realm()
+            try realm.write {
+                let result = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first
+                result?.offlineDate = Date()
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
     @discardableResult
     func setDirectory(serverUrl: String, richWorkspace: String?, account: String) -> tableDirectory? {
-
         var result: tableDirectory?
 
         do {

+ 8 - 8
iOSClient/Data/NCManageDatabase+LocalFile.swift

@@ -62,7 +62,7 @@ extension NCManageDatabase {
     // MARK: -
     // MARK: Table LocalFile
 
-    func addLocalFile(metadata: tableMetadata) {
+    func addLocalFile(metadata: tableMetadata, offline: Bool? = nil) {
 
         do {
             let realm = try Realm()
@@ -75,6 +75,9 @@ extension NCManageDatabase {
                 addObject.exifLongitude = "-1"
                 addObject.ocId = metadata.ocId
                 addObject.fileName = metadata.fileName
+                if let offline {
+                    addObject.offline = offline
+                }
                 realm.add(addObject, update: .all)
             }
         } catch let error {
@@ -115,18 +118,15 @@ extension NCManageDatabase {
         }
     }
 
-    func setLocalFile(ocId: String, fileName: String?, etag: String?) {
+    func setLocalFile(ocId: String, fileName: String?) {
 
         do {
             let realm = try Realm()
             try realm.write {
                 let result = realm.objects(tableLocalFile.self).filter("ocId == %@", ocId).first
-                if let fileName = fileName {
+                if let fileName {
                     result?.fileName = fileName
                 }
-                if let etag = etag {
-                    result?.etag = etag
-                }
             }
         } catch let error {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
@@ -152,13 +152,13 @@ extension NCManageDatabase {
         }
     }
 
-    func setLocalFile(ocId: String, offline: Bool) {
+    func setOffLocalFile(ocId: String) {
 
         do {
             let realm = try Realm()
             try realm.write {
                 let result = realm.objects(tableLocalFile.self).filter("ocId == %@", ocId).first
-                result?.offline = offline
+                result?.offline = false
             }
         } catch let error {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")

+ 175 - 0
iOSClient/Data/NCManageDatabase+Metadata+Session.swift

@@ -0,0 +1,175 @@
+//
+//  NCManageDatabase+Metadata+Session.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 12/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import RealmSwift
+import NextcloudKit
+
+extension NCManageDatabase {
+
+    func setMetadataSession(ocId: String,
+                            newFileName: String? = nil,
+                            session: String? = nil,
+                            sessionError: String? = nil,
+                            selector: String? = nil,
+                            status: Int? = nil,
+                            etag: String? = nil,
+                            errorCode: Int? = nil) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
+                    if let newFileName = newFileName {
+                        result.fileName = newFileName
+                        result.fileNameView = newFileName
+                    }
+                    if let session {
+                        result.session = session
+                    }
+                    if let sessionError {
+                        result.sessionError = sessionError
+                        if sessionError.isEmpty {
+                            result.errorCode = 0
+                        }
+                    }
+                    if let selector {
+                        result.sessionSelector = selector
+                    }
+                    if let status {
+                        result.status = status
+                        if status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload {
+                            result.sessionDate = Date()
+                        } else if status == NCGlobal.shared.metadataStatusNormal {
+                            result.sessionDate = nil
+                        }
+                    }
+                    if let etag {
+                        result.etag = etag
+                    }
+                    if let errorCode {
+                        result.errorCode = errorCode
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    func setMetadataSession(ocId: String,
+                            status: Int? = nil,
+                            taskIdentifier: Int? = nil) {
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
+                    if let status {
+                        result.status = status
+                        if status == NCGlobal.shared.metadataStatusWaitDownload || status == NCGlobal.shared.metadataStatusWaitUpload {
+                            result.sessionDate = Date()
+                        } else if status == NCGlobal.shared.metadataStatusNormal {
+                            result.sessionDate = nil
+                        }
+                    }
+                    if let taskIdentifier {
+                        result.sessionTaskIdentifier = taskIdentifier
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @discardableResult
+    func setMetadatasSessionInWaitDownload(metadatas: [tableMetadata], session: String, selector: String) -> tableMetadata? {
+        if metadatas.isEmpty { return nil }
+        var metadataUpdated: tableMetadata?
+
+        do {
+            let realm = try Realm()
+            try realm.write {
+                for metadata in metadatas {
+                    if let result = realm.objects(tableMetadata.self).filter("ocId == %@", metadata.ocId).first {
+                        result.session = session
+                        result.sessionError = ""
+                        result.sessionSelector = selector
+                        result.status = NCGlobal.shared.metadataStatusWaitDownload
+                        result.sessionDate = Date()
+                        metadataUpdated = tableMetadata(value: result)
+                    } else {
+                        metadata.session = session
+                        metadata.sessionError = ""
+                        metadata.sessionSelector = selector
+                        metadata.status = NCGlobal.shared.metadataStatusWaitDownload
+                        metadata.sessionDate = Date()
+                        realm.add(metadata, update: .all)
+                        metadataUpdated = tableMetadata(value: metadata)
+                    }
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+
+        return metadataUpdated
+    }
+
+    func clearMetadataSession(metadatas: Results<tableMetadata>) {
+        do {
+            let realm = try Realm()
+            try realm.write {
+                for metadata in metadatas {
+                    metadata.session = ""
+                    metadata.sessionError = ""
+                    metadata.sessionSelector = ""
+                    metadata.sessionDate = nil
+                    metadata.status = NCGlobal.shared.metadataStatusNormal
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
+    @discardableResult
+    func setMetadataStatus(ocId: String, status: Int) -> tableMetadata? {
+        var result: tableMetadata?
+        do {
+            let realm = try Realm()
+            try realm.write {
+                result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first
+                result?.status = status
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+        if let result {
+            return tableMetadata.init(value: result)
+        } else {
+            return nil
+        }
+    }
+}

+ 57 - 120
iOSClient/Data/NCManageDatabase+Metadata.swift

@@ -100,6 +100,7 @@ class tableMetadata: Object, NCUserBaseUrl {
     @objc dynamic var richWorkspace: String?
     @objc dynamic var serverUrl = ""
     @objc dynamic var session = ""
+    @objc dynamic var sessionDate: Date?
     @objc dynamic var sessionError = ""
     @objc dynamic var sessionSelector = ""
     @objc dynamic var sessionTaskIdentifier: Int = 0
@@ -209,19 +210,19 @@ extension tableMetadata {
         return true
     }
 
-    var isSettableOnOffline: Bool {
-        return session.isEmpty && !isDocumentViewableOnly
+    var canSetAsAvailableOffline: Bool {
+        return session.isEmpty && !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted
     }
 
     var canOpenIn: Bool {
         return session.isEmpty && !isDocumentViewableOnly && !directory && !NCBrandOptions.shared.disable_openin_file
     }
 
-    var isDirectoySettableE2EE: Bool {
+    var canSetDirectoryAsE2EE: Bool {
         return directory && size == 0 && !e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account)
     }
 
-    var isDirectoryUnsettableE2EE: Bool {
+    var canUnsetDirectoryAsE2EE: Bool {
         return !isDirectoryE2EE && directory && size == 0 && e2eEncrypted && NCKeychain().isEndToEndEnabled(account: account)
     }
 
@@ -271,14 +272,6 @@ extension tableMetadata {
         !isFlaggedAsLivePhotoByServer
     }
 
-    var isSynchronizable: Bool {
-        let localFile = NCManageDatabase.shared.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", ocId))?.first
-        if localFile?.etag != etag || NCUtilityFileSystem().fileProviderStorageSize(ocId, fileNameView: fileNameView) == 0 {
-            return true
-        }
-        return false
-    }
-
     /// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else
     func canUnlock(as user: String) -> Bool {
         return !lock || (lockOwner == user && lockOwnerType == 0)
@@ -501,6 +494,24 @@ extension NCManageDatabase {
         return tableMetadata.init(value: result)
     }
 
+    func addMetadatasWithoutUpdate(_ metadatas: [tableMetadata]) {
+        if metadatas.isEmpty { return }
+
+        do {
+            let realm = try Realm()
+            for metadata in metadatas {
+                if realm.objects(tableMetadata.self).filter("ocId == %@", metadata.ocId).first != nil {
+                    return
+                }
+                try realm.write {
+                    realm.add(metadata, update: .all)
+                }
+            }
+        } catch let error {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+        }
+    }
+
     func addMetadatas(_ metadatas: [tableMetadata]) {
 
         do {
@@ -571,113 +582,6 @@ extension NCManageDatabase {
         }
     }
 
-    func setMetadataSession(ocId: String,
-                            newFileName: String? = nil,
-                            session: String? = nil,
-                            sessionError: String? = nil,
-                            selector: String? = nil,
-                            status: Int? = nil,
-                            etag: String? = nil,
-                            errorCode: Int? = nil) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
-                    if let newFileName = newFileName {
-                        result.fileName = newFileName
-                        result.fileNameView = newFileName
-                    }
-                    if let session {
-                        result.session = session
-                    }
-                    if let sessionError {
-                        result.sessionError = sessionError
-                    }
-                    if let selector {
-                        result.sessionSelector = selector
-                    }
-                    if let status {
-                        result.status = status
-                    }
-                    if let etag {
-                        result.etag = etag
-                    }
-                    if let errorCode {
-                        result.errorCode = errorCode
-                    }
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func setMetadataSession(ocId: String,
-                            status: Int? = nil,
-                            taskIdentifier: Int? = nil) {
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
-                    if let status {
-                        result.status = status
-                    }
-                    if let taskIdentifier {
-                        result.sessionTaskIdentifier = taskIdentifier
-                    }
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-    }
-
-    func setMetadataSessionInWaitDownload(ocId: String, selector: String) -> tableMetadata? {
-
-        var metadata: tableMetadata?
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                if let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first {
-                    result.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
-                    result.sessionError = ""
-                    result.sessionSelector = selector
-                    result.status = NCGlobal.shared.metadataStatusWaitDownload
-                    metadata = tableMetadata(value: result)
-                }
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        return metadata
-    }
-
-    @discardableResult
-    func setMetadataStatus(ocId: String, status: Int) -> tableMetadata? {
-
-        var result: tableMetadata?
-
-        do {
-            let realm = try Realm()
-            try realm.write {
-                result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first
-                result?.status = status
-            }
-        } catch let error {
-            NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
-        }
-
-        if let result = result {
-            return tableMetadata.init(value: result)
-        } else {
-            return nil
-        }
-    }
-
     func setMetadataEtagResource(ocId: String, etagResource: String?) {
 
         guard let etagResource = etagResource else { return }
@@ -922,6 +826,39 @@ extension NCManageDatabase {
         return nil
     }
 
+    func getMetadataFromFileName(_ fileName: String, serverUrl: String) -> tableMetadata? {
+
+        do {
+            let realm = try Realm()
+            realm.refresh()
+            guard let result = realm.objects(tableMetadata.self).filter("fileName == %@ AND serverUrl == %@", fileName, serverUrl).first else { return nil }
+            return tableMetadata.init(value: result)
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+        }
+
+        return nil
+    }
+
+    func getMetadataFromFileNameLocalPath(_ fileNameLocalPath: String?) -> tableMetadata? {
+
+        let components = fileNameLocalPath?.components(separatedBy: "/")
+        if let count = components?.count,
+           components?.count ?? 0 > 2,
+           let ocId = components?[count - 2] {
+            do {
+                let realm = try Realm()
+                realm.refresh()
+                guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first else { return nil }
+                return tableMetadata.init(value: result)
+            } catch let error as NSError {
+                NextcloudKit.shared.nkCommonInstance.writeLog("Could not access database: \(error)")
+            }
+        }
+
+        return nil
+    }
+
     func getTableMetadataFromOcId(_ ocId: String?) -> tableMetadata? {
 
         guard let ocId else { return nil }
@@ -1003,7 +940,7 @@ extension NCManageDatabase {
         do {
             let realm = try Realm()
             try realm.write {
-                let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %@)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError)
+                let results = realm.objects(tableMetadata.self).filter("account == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploadError)
                 realm.delete(results)
             }
         } catch let error {

+ 6 - 0
iOSClient/Extensions/Array+Extension.swift

@@ -38,3 +38,9 @@ extension Array {
         return arrayOrdered
     }
 }
+
+extension Array where Element == URLQueryItem {
+    subscript(name: String) -> URLQueryItem? {
+        first(where: { $0.name == name })
+    }
+}

+ 0 - 33
iOSClient/Extensions/NSNotificationCenter+MainThread.h

@@ -1,33 +0,0 @@
-//
-//  NSNotificationCenter+MainThread.h
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 28/05/17.
-//  Copyright (c) 2017 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-#import <Foundation/Foundation.h>
-#import <UIKit/UIKit.h>
-
-@interface NSNotificationCenter (MainThread)
-
-- (void)postNotificationOnMainThread:(NSNotification *)notification;
-- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject;
-- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
-
-@end

+ 0 - 45
iOSClient/Extensions/NSNotificationCenter+MainThread.m

@@ -1,45 +0,0 @@
-//
-//  NSNotificationCenter+MainThread.m
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 28/05/17.
-//  Copyright (c) 2017 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-#import "NSNotificationCenter+MainThread.h"
-
-@implementation NSNotificationCenter (MainThread)
-
-- (void)postNotificationOnMainThread:(NSNotification *)notification
-{
-    [self performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:YES];
-}
-
-- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject
-{
-    NSNotification *notification = [NSNotification notificationWithName:aName object:anObject];
-    [self postNotificationOnMainThread:notification];
-}
-
-- (void)postNotificationOnMainThreadName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
-{
-    NSNotification *notification = [NSNotification notificationWithName:aName object:anObject userInfo:aUserInfo];
-    [self postNotificationOnMainThread:notification];
-}
-
-@end

+ 9 - 0
iOSClient/Extensions/UIView+Extension.swift

@@ -45,4 +45,13 @@ extension UIView {
         hiddenView.fillSuperview()
         hiddenView.addSubview(view)
     }
+
+    func addBlur(style: UIBlurEffect.Style) {
+        let blur = UIBlurEffect(style: style)
+        let blurredEffectView = UIVisualEffectView(effect: blur)
+        blurredEffectView.frame = self.bounds
+        blurredEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        blurredEffectView.isUserInteractionEnabled = false
+        self.addSubview(blurredEffectView)
+    }
 }

+ 4 - 0
iOSClient/Extensions/View+Extension.swift

@@ -33,4 +33,8 @@ extension View {
     func frameForPreview() -> some View {
         return frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
     }
+
+    func hiddenConditionally(isHidden: Bool) -> some View {
+        isHidden ? AnyView(self.hidden()) : AnyView(self)
+    }
 }

+ 0 - 1
iOSClient/Favorites/NCFavorite.swift

@@ -34,7 +34,6 @@ class NCFavorite: NCCollectionViewCommon {
         titleCurrentFolder = NSLocalizedString("_favorites_", comment: "")
         layoutKey = NCGlobal.shared.layoutViewFavorite
         enableSearchBar = false
-        headerMenuButtonsView = true
         headerRichWorkspaceDisable = true
         emptyImage = UIImage(named: "star.fill")?.image(color: NCBrandColor.shared.yellowFavorite, size: UIScreen.main.bounds.width)
         emptyTitle = "_favorite_no_files_"

+ 0 - 1
iOSClient/Files/NCFiles.swift

@@ -38,7 +38,6 @@ class NCFiles: NCCollectionViewCommon {
         titleCurrentFolder = NCBrandOptions.shared.brand
         layoutKey = NCGlobal.shared.layoutViewFiles
         enableSearchBar = true
-        headerMenuButtonsView = true
         headerRichWorkspaceDisable = false
         headerMenuTransferView = true
         emptyImage = UIImage(named: "folder")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)

+ 0 - 1
iOSClient/Groupfolders/NCGroupfolders.swift

@@ -34,7 +34,6 @@ class NCGroupfolders: NCCollectionViewCommon {
         titleCurrentFolder = NSLocalizedString("_group_folders_", comment: "")
         layoutKey = NCGlobal.shared.layoutViewGroupfolders
         enableSearchBar = false
-        headerMenuButtonsView = true
         headerRichWorkspaceDisable = true
         emptyImage = UIImage(named: "folder_group")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)
         emptyTitle = "_files_no_files_"

+ 422 - 111
iOSClient/Main/Collection Common/NCCollectionViewCommon.swift

@@ -28,56 +28,59 @@ import EasyTipView
 import JGProgressHUD
 import Queuer
 
-class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, UIAdaptivePresentationControllerDelegate, NCEmptyDataSetDelegate, UIContextMenuInteractionDelegate, NCAccountRequestDelegate, NCSelectableNavigationView {
+class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate, NCListCellDelegate, NCGridCellDelegate, NCSectionHeaderMenuDelegate, NCSectionFooterDelegate, UIAdaptivePresentationControllerDelegate, NCEmptyDataSetDelegate, UIContextMenuInteractionDelegate, NCAccountRequestDelegate {
 
     @IBOutlet weak var collectionView: UICollectionView!
 
-    internal let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
-    internal let utilityFileSystem = NCUtilityFileSystem()
-    internal let utility = NCUtility()
-    internal let refreshControl = UIRefreshControl()
-    internal var searchController: UISearchController?
-    internal var emptyDataSet: NCEmptyDataSet?
-    internal var backgroundImageView = UIImageView()
-    internal var serverUrl: String = ""
-    internal var isEditMode = false
-    internal var selectOcId: [String] = []
-    internal var selectIndexPath: [IndexPath] = []
-    internal var metadataFolder: tableMetadata?
-    internal var dataSource = NCDataSource()
-    internal var richWorkspaceText: String?
-    internal var headerMenu: NCSectionHeaderMenu?
-    internal var isSearchingMode: Bool = false
-    internal var layoutForView: NCDBLayoutForView?
-    internal var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() }
     private var autoUploadFileName = ""
     private var autoUploadDirectory = ""
-    internal var groupByField = "name"
-    internal var providers: [NKSearchProvider]?
-    internal var searchResults: [NKSearchResult]?
-    internal var listLayout: NCListLayout!
-    internal var gridLayout: NCGridLayout!
-    internal var literalSearch: String?
-    internal var isReloadDataSourceNetworkInProgress: Bool = false
-
-    internal var timerNotificationCenter: Timer?
-    internal var notificationReloadDataSource: Int = 0
-    internal var notificationReloadDataSourceNetwork: Int = 0
 
     private var pushed: Bool = false
     private var tipView: EasyTipView?
     private var isTransitioning: Bool = false
+
+    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    let utilityFileSystem = NCUtilityFileSystem()
+    let utility = NCUtility()
+    let refreshControl = UIRefreshControl()
+    var searchController: UISearchController?
+    var emptyDataSet: NCEmptyDataSet?
+    var backgroundImageView = UIImageView()
+    var serverUrl: String = ""
+    var isEditMode = false
+    var selectOcId: [String] = []
+    var selectIndexPath: [IndexPath] = []
+    var metadataFolder: tableMetadata?
+    var dataSource = NCDataSource()
+    var richWorkspaceText: String?
+    var headerMenu: NCSectionHeaderMenu?
+    var isSearchingMode: Bool = false
+    var layoutForView: NCDBLayoutForView?
+    var selectableDataSource: [RealmSwiftObject] { dataSource.getMetadataSourceForAllSections() }
+
+    var groupByField = "name"
+    var providers: [NKSearchProvider]?
+    var searchResults: [NKSearchResult]?
+    var listLayout: NCListLayout!
+    var gridLayout: NCGridLayout!
+    var literalSearch: String?
+    var isReloadDataSourceNetworkInProgress: Bool = false
+    var tabBarSelect: NCSelectableViewTabBar?
+
+    var timerNotificationCenter: Timer?
+    var notificationReloadDataSource: Int = 0
+    var notificationReloadDataSourceNetwork: Int = 0
+
     // DECLARE
-    internal var layoutKey = ""
-    internal var titleCurrentFolder = ""
-    internal var titlePreviusFolder: String?
-    internal var enableSearchBar: Bool = false
-    internal var headerMenuTransferView = false
-    internal var headerMenuButtonsView: Bool = true
-    internal var headerRichWorkspaceDisable: Bool = false
-    internal var emptyImage: UIImage?
-    internal var emptyTitle: String = ""
-    internal var emptyDescription: String = ""
+    var layoutKey = ""
+    var titleCurrentFolder = ""
+    var titlePreviusFolder: String?
+    var enableSearchBar: Bool = false
+    var headerMenuTransferView = false
+    var headerRichWorkspaceDisable: Bool = false
+    var emptyImage: UIImage?
+    var emptyTitle: String = ""
+    var emptyDescription: String = ""
 
     // MARK: - View Life Cycle
 
@@ -88,6 +91,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     override func viewDidLoad() {
         super.viewDidLoad()
 
+        tabBarSelect = NCCollectionViewCommonSelectTabBar(tabBarController: tabBarController, delegate: self)
+
         self.navigationController?.presentationController?.delegate = self
 
         // CollectionView & layout
@@ -255,6 +260,9 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         // TIP
         self.tipView?.dismiss()
+
+        isEditMode = false
+        setNavigationItems()
     }
 
     func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
@@ -281,6 +289,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         return true
     }
 
+    override func viewWillLayoutSubviews() {
+        super.viewWillLayoutSubviews()
+
+        if let frame = tabBarController?.tabBar.frame {
+            (tabBarSelect as? NCCollectionViewCommonSelectTabBar)?.hostingController?.view.frame = frame
+        }
+    }
+
     // MARK: - NotificationCenter
 
     @objc func notificationCenterEvents() {
@@ -363,7 +379,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
               account == appDelegate.account
         else { return }
 
-        reloadDataSourceNetwork()
+        notificationReloadDataSource += 1
     }
 
     @objc func createFolder(_ notification: NSNotification) {
@@ -720,46 +736,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         appDelegate.openLogin(viewController: self, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
     }
 
-    func tapButtonSwitch(_ sender: Any) {
-
-        guard isTransitioning == false else { return }
-        isTransitioning = true
-
-        if layoutForView?.layout == NCGlobal.shared.layoutGrid {
-
-            // list layout
-            headerMenu?.buttonSwitch.accessibilityLabel = NSLocalizedString("_grid_view_", comment: "")
-            layoutForView?.layout = NCGlobal.shared.layoutList
-            NCManageDatabase.shared.setLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout)
-            self.groupByField = "name"
-            if self.dataSource.groupByField != self.groupByField {
-                self.dataSource.changeGroupByField(self.groupByField)
-            }
-
-            self.collectionView.reloadData()
-            self.collectionView.collectionViewLayout.invalidateLayout()
-            self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) {_ in self.isTransitioning = false }
-        } else {
-
-            // grid layout
-            headerMenu?.buttonSwitch.accessibilityLabel = NSLocalizedString("_list_view_", comment: "")
-            layoutForView?.layout = NCGlobal.shared.layoutGrid
-            NCManageDatabase.shared.setLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout)
-            if isSearchingMode {
-                self.groupByField = "name"
-            } else {
-                self.groupByField = "classFile"
-            }
-            if self.dataSource.groupByField != self.groupByField {
-                self.dataSource.changeGroupByField(self.groupByField)
-            }
-
-            self.collectionView.reloadData()
-            self.collectionView.collectionViewLayout.invalidateLayout()
-            self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) {_ in self.isTransitioning = false }
-        }
-    }
-
     func tapButtonOrder(_ sender: Any) {
 
         let sortMenu = NCSortMenu()
@@ -788,7 +764,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
             toggleMenu(metadata: metadata, indexPath: indexPath, imageIcon: image)
         } else if namedButtonMore == NCGlobal.shared.buttonMoreStop {
             Task {
-                await NCNetworking.shared.cancel(metadata: metadata)
+                await cancelSession(metadata: metadata)
             }
         }
     }
@@ -815,7 +791,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
         if let ocId = NCNetworking.shared.transferInForegorund?.ocId,
            let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
             Task {
-                await NCNetworking.shared.cancel(metadata: metadata)
+                await cancelSession(metadata: metadata)
             }
         }
     }
@@ -904,8 +880,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
         DispatchQueue.global().async {
             if withQueryDB { self.queryDB() }
+            self.isReloadDataSourceNetworkInProgress = false
             DispatchQueue.main.async {
-                self.isReloadDataSourceNetworkInProgress = false
                 self.refreshControl.endRefreshing()
                 self.collectionView.reloadData()
             }
@@ -913,9 +889,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     }
 
     @objc func reloadDataSourceNetwork() {
-
-        isReloadDataSourceNetworkInProgress = true
-        collectionView?.reloadData()
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
+            self.isReloadDataSourceNetworkInProgress = true
+            self.collectionView?.reloadData()
+        }
     }
 
     @objc func networkSearch() {
@@ -1030,7 +1007,6 @@ extension NCCollectionViewCommon: NCEndToEndInitializeDelegate {
 extension NCCollectionViewCommon: UICollectionViewDelegate {
 
     func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
-
         guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return }
         appDelegate.activeMetadata = metadata
         let metadataSourceForAllSections = dataSource.getMetadataSourceForAllSections()
@@ -1044,6 +1020,9 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
                 selectIndexPath.append(indexPath)
             }
             collectionView.reloadItems(at: [indexPath])
+
+            self.setNavigationRightItems()
+
             return
         }
 
@@ -1082,7 +1061,10 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
 
             if utilityFileSystem.fileProviderStorageExists(metadata) {
                 NCViewer().view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: imageIcon)
-            } else if NextcloudKit.shared.isNetworkReachable(), let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileView) {
+            } else if NextcloudKit.shared.isNetworkReachable(),
+                      let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                               session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                               selector: NCGlobal.shared.selectorLoadFileView) {
                 NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
             } else {
                 let error = NKError(errorCode: NCGlobal.shared.errorOffline, errorDescription: "_go_online_")
@@ -1212,6 +1194,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
 
         let numberItems = dataSource.numberOfItemsInSection(section)
         emptyDataSet?.numberOfItemsInSection(numberItems, section: section)
+
+        setNavigationRightItems()
+
         return numberItems
     }
 
@@ -1221,12 +1206,12 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
 
         // LAYOUT LIST
         if layoutForView?.layout == NCGlobal.shared.layoutList {
-            guard let listCell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell else { return UICollectionViewCell() }
+            guard let listCell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell else { return NCListCell() }
             listCell.delegate = self
             cell = listCell
         } else {
         // LAYOUT GRID
-            guard let gridCell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell else { return UICollectionViewCell() }
+            guard let gridCell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell else { return NCGridCell() }
             gridCell.delegate = self
             cell = gridCell
         }
@@ -1456,14 +1441,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
 
                 self.headerMenu = header
 
-                if layoutForView?.layout == NCGlobal.shared.layoutGrid {
-                    header.setImageSwitchList()
-                    header.buttonSwitch.accessibilityLabel = NSLocalizedString("_list_view_", comment: "")
-                } else {
-                    header.setImageSwitchGrid()
-                    header.buttonSwitch.accessibilityLabel = NSLocalizedString("_grid_view_", comment: "")
-                }
-
                 header.delegate = self
 
                 if !isSearchingMode, headerMenuTransferView, let ocId = NCNetworking.shared.transferInForegorund?.ocId {
@@ -1473,14 +1450,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
                     header.setViewTransfer(isHidden: true)
                 }
 
-                if headerMenuButtonsView {
-                    header.setStatusButtonsView(enable: !dataSource.getMetadataSourceForAllSections().isEmpty)
-                    header.setButtonsView(height: NCGlobal.shared.heightButtonsView)
-                    header.setSortedTitle(layoutForView?.titleButtonHeader ?? "")
-                } else {
-                    header.setButtonsView(height: 0)
-                }
-
                 header.setRichWorkspaceHeight(heightHeaderRichWorkspace)
                 header.setRichWorkspaceText(richWorkspaceText)
 
@@ -1571,10 +1540,6 @@ extension NCCollectionViewCommon: UICollectionViewDelegateFlowLayout {
             NCNetworking.shared.transferInForegorund = nil
         }
 
-        if headerMenuButtonsView {
-            size += NCGlobal.shared.heightButtonsView
-        }
-
         return size
     }
 
@@ -1644,6 +1609,273 @@ extension NCCollectionViewCommon: EasyTipViewDelegate {
     }
 }
 
+extension NCCollectionViewCommon: NCSelectableNavigationView, NCCollectionViewCommonSelectTabBarDelegate {
+    func setNavigationRightItems() {
+        var selectedMetadatas: [tableMetadata] = []
+        var isAnyOffline = false
+        var isAnyDirectory = false
+        var isAllDirectory = true
+        var isAnyLocked = false
+        var canUnlock = true
+        var canSetAsOffline = true
+
+        for ocId in selectOcId {
+            guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { continue }
+            selectedMetadatas.append(metadata)
+
+            if metadata.directory {
+                isAnyDirectory = true
+            } else {
+                isAllDirectory = false
+            }
+
+            if !metadata.canSetAsAvailableOffline {
+                canSetAsOffline = false
+            }
+
+            if metadata.lock {
+                isAnyLocked = true
+                if metadata.lockOwner != appDelegate.userId {
+                    canUnlock = false
+                }
+            }
+
+            guard !isAnyOffline else { continue }
+            if metadata.directory,
+               let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", appDelegate.account, metadata.serverUrl + "/" + metadata.fileName)) {
+                isAnyOffline = directory.offline
+            } else if let localFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) {
+                isAnyOffline = localFile.offline
+            } // else: file is not offline, continue
+        }
+
+        guard let tabBarSelect = tabBarSelect as? NCCollectionViewCommonSelectTabBar else { return }
+
+        tabBarSelect.isAnyOffline = isAnyOffline
+        tabBarSelect.canSetAsOffline = canSetAsOffline
+        tabBarSelect.isAnyDirectory = isAnyDirectory
+        tabBarSelect.isAllDirectory = isAllDirectory
+        tabBarSelect.isAnyLocked = isAnyLocked
+        tabBarSelect.canUnlock = canUnlock
+        tabBarSelect.enableLock = !isAnyDirectory && canUnlock && !NCGlobal.shared.capabilityFilesLockVersion.isEmpty
+        tabBarSelect.isSelectedEmpty = selectOcId.isEmpty
+        tabBarSelect.selectedMetadatas = selectedMetadatas
+
+        if isEditMode {
+            tabBarSelect.show()
+
+            let select = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .done) { self.toggleSelect() }
+
+            navigationItem.rightBarButtonItems = [select]
+        } else {
+            tabBarSelect.hide()
+
+            let notification = UIBarButtonItem(image: .init(systemName: "bell"), style: .plain, action: tapNotification)
+
+            let menu = UIMenu(children: createMenuActions())
+            let menuButton = UIBarButtonItem(image: .init(systemName: "ellipsis.circle"), menu: menu)
+
+            if layoutKey == NCGlobal.shared.layoutViewFiles {
+                navigationItem.rightBarButtonItems = [menuButton, notification]
+            } else {
+                navigationItem.rightBarButtonItems = [menuButton]
+            }
+        }
+    }
+
+    func onListSelected() {
+        if layoutForView?.layout == NCGlobal.shared.layoutGrid {
+            layoutForView?.layout = NCGlobal.shared.layoutList
+            NCManageDatabase.shared.setLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout)
+            self.groupByField = "name"
+            if self.dataSource.groupByField != self.groupByField {
+                self.dataSource.changeGroupByField(self.groupByField)
+            }
+
+            self.collectionView.reloadData()
+            self.collectionView.collectionViewLayout.invalidateLayout()
+            self.collectionView.setCollectionViewLayout(self.listLayout, animated: true) {_ in self.isTransitioning = false }
+        }
+    }
+
+    func onGridSelected() {
+        if layoutForView?.layout == NCGlobal.shared.layoutList {
+            layoutForView?.layout = NCGlobal.shared.layoutGrid
+            NCManageDatabase.shared.setLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl, layout: layoutForView?.layout)
+            if isSearchingMode {
+                self.groupByField = "name"
+            } else {
+                self.groupByField = "classFile"
+            }
+            if self.dataSource.groupByField != self.groupByField {
+                self.dataSource.changeGroupByField(self.groupByField)
+            }
+
+            self.collectionView.reloadData()
+            self.collectionView.collectionViewLayout.invalidateLayout()
+            self.collectionView.setCollectionViewLayout(self.gridLayout, animated: true) {_ in self.isTransitioning = false }
+        }
+    }
+
+    func selectAll() {
+        collectionViewSelectAll()
+    }
+
+    func delete(selectedMetadatas: [tableMetadata]) {
+        let alertController = UIAlertController(
+            title: NSLocalizedString("_confirm_delete_selected_", comment: ""),
+            message: nil,
+            preferredStyle: .alert)
+
+        let canDeleteServer = selectedMetadatas.allSatisfy { !$0.lock }
+
+        if canDeleteServer {
+            let copyMetadatas = selectedMetadatas
+
+            alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .destructive) { _ in
+                Task {
+                    var error = NKError()
+                    var ocId: [String] = []
+                    for metadata in copyMetadatas where error == .success {
+                        error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false)
+                        if error == .success {
+                            ocId.append(metadata.ocId)
+                        }
+                    }
+                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "indexPath": self.selectIndexPath, "onlyLocalCache": false, "error": error])
+                }
+
+                self.toggleSelect()
+            })
+        }
+
+        alertController.addAction(UIAlertAction(title: NSLocalizedString("_remove_local_file_", comment: ""), style: .default) { (_: UIAlertAction) in
+            let copyMetadatas = selectedMetadatas
+
+            Task {
+                var error = NKError()
+                var ocId: [String] = []
+                for metadata in copyMetadatas where error == .success {
+                    error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: true)
+                    if error == .success {
+                        ocId.append(metadata.ocId)
+                    }
+                }
+                if error != .success {
+                    NCContentPresenter().showError(error: error)
+                }
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "indexPath": self.selectIndexPath, "onlyLocalCache": true, "error": error])
+                self.toggleSelect()
+            }
+        })
+
+        alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) { (_: UIAlertAction) in })
+        self.viewController.present(alertController, animated: true, completion: nil)
+    }
+
+    func move(selectedMetadatas: [tableMetadata]) {
+        NCActionCenter.shared.openSelectView(items: selectedMetadatas, indexPath: self.selectIndexPath)
+        self.toggleSelect()
+    }
+
+    func share(selectedMetadatas: [tableMetadata]) {
+        NCActionCenter.shared.openActivityViewController(selectedMetadata: selectedMetadatas)
+        self.toggleSelect()
+    }
+
+    func saveAsAvailableOffline(selectedMetadatas: [tableMetadata], isAnyOffline: Bool) {
+        if !isAnyOffline, selectedMetadatas.count > 3 {
+            let alert = UIAlertController(
+                title: NSLocalizedString("_set_available_offline_", comment: ""),
+                message: NSLocalizedString("_select_offline_warning_", comment: ""),
+                preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: NSLocalizedString("_continue_", comment: ""), style: .default, handler: { _ in
+                selectedMetadatas.forEach { NCActionCenter.shared.setMetadataAvalableOffline($0, isOffline: isAnyOffline) }
+                self.toggleSelect()
+            }))
+            alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel))
+            self.viewController.present(alert, animated: true)
+        } else {
+            selectedMetadatas.forEach { NCActionCenter.shared.setMetadataAvalableOffline($0, isOffline: isAnyOffline) }
+            self.toggleSelect()
+        }
+    }
+
+    func lock(selectedMetadatas: [tableMetadata], isAnyLocked: Bool) {
+        for metadata in selectedMetadatas where metadata.lock == isAnyLocked {
+            NCNetworking.shared.lockUnlockFile(metadata, shoulLock: !isAnyLocked)
+        }
+
+        self.toggleSelect()
+    }
+
+    func createMenuActions() -> [UIMenuElement] {
+        guard let layoutForView = NCManageDatabase.shared.getLayoutForView(account: appDelegate.account, key: layoutKey, serverUrl: serverUrl) else { return [] }
+
+        let select = UIAction(title: NSLocalizedString("_select_", comment: ""), image: .init(systemName: "checkmark.circle"), attributes: selectableDataSource.isEmpty ? .disabled : []) { _ in self.toggleSelect() }
+
+        let list = UIAction(title: NSLocalizedString("_list_", comment: ""), image: .init(systemName: "list.bullet"), state: layoutForView.layout == NCGlobal.shared.layoutList ? .on : .off) { _ in
+            self.onListSelected()
+            self.setNavigationRightItems()
+        }
+
+        let grid = UIAction(title: NSLocalizedString("_icons_", comment: ""), image: .init(systemName: "square.grid.2x2"), state: layoutForView.layout == NCGlobal.shared.layoutGrid ? .on : .off) { _ in
+            self.onGridSelected()
+            self.setNavigationRightItems()
+        }
+
+        let viewStyleSubmenu = UIMenu(title: "", options: .displayInline, children: [list, grid])
+
+        let ascending = layoutForView.ascending
+        let ascendingChevronImage = UIImage(systemName: ascending ? "chevron.up" : "chevron.down")
+        let isName = layoutForView.sort == "fileName"
+        let isDate = layoutForView.sort == "date"
+        let isSize = layoutForView.sort == "size"
+
+        let byName = UIAction(title: NSLocalizedString("_name_", comment: ""), image: isName ? ascendingChevronImage : nil, state: isName ? .on : .off) { _ in
+            if isName { // repeated press
+                layoutForView.ascending = !layoutForView.ascending
+            }
+
+            layoutForView.sort = "fileName"
+            self.saveLayout(layoutForView)
+        }
+
+        let byNewest = UIAction(title: NSLocalizedString("_date_", comment: ""), image: isDate ? ascendingChevronImage : nil, state: isDate ? .on : .off) { _ in
+            if isDate { // repeated press
+                layoutForView.ascending = !layoutForView.ascending
+            }
+
+            layoutForView.sort = "date"
+            self.saveLayout(layoutForView)
+        }
+
+        let byLargest = UIAction(title: NSLocalizedString("_size_", comment: ""), image: isSize ? ascendingChevronImage : nil, state: isSize ? .on : .off) { _ in
+            if isSize { // repeated press
+                layoutForView.ascending = !layoutForView.ascending
+            }
+
+            layoutForView.sort = "size"
+            self.saveLayout(layoutForView)
+        }
+
+        let sortSubmenu = UIMenu(title: NSLocalizedString("_order_by_", comment: ""), options: .displayInline, children: [byName, byNewest, byLargest])
+
+        let foldersOnTop = UIAction(title: NSLocalizedString("_directory_on_top_no_", comment: ""), image: UIImage(systemName: "folder"), state: layoutForView.directoryOnTop ? .on : .off) { _ in
+            layoutForView.directoryOnTop = !layoutForView.directoryOnTop
+            self.saveLayout(layoutForView)
+        }
+
+        let foldersSubmenu = UIMenu(title: "", options: .displayInline, children: [foldersOnTop])
+
+        if layoutKey == NCGlobal.shared.layoutViewRecent {
+            return [select]
+        } else {
+            return [select, viewStyleSubmenu, sortSubmenu, foldersSubmenu]
+        }
+    }
+}
+
 extension NCCollectionViewCommon {
 
     func getAvatarFromIconUrl(metadata: tableMetadata) -> String? {
@@ -1662,6 +1894,85 @@ extension NCCollectionViewCommon {
         }
         return ownerId
     }
+
+    // MARK: - Cancel (Download Upload)
+
+    // sessionIdentifierDownload: String = "com.nextcloud.nextcloudkit.session.download"
+    // sessionIdentifierUpload: String = "com.nextcloud.nextcloudkit.session.upload"
+
+    // sessionUploadBackground: String = "com.nextcloud.session.upload.background"
+    // sessionUploadBackgroundWWan: String = "com.nextcloud.session.upload.backgroundWWan"
+    // sessionUploadBackgroundExtension: String = "com.nextcloud.session.upload.extension"
+
+    func cancelSession(metadata: tableMetadata) async {
+
+        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+        utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+
+        // No session found
+        if metadata.session.isEmpty {
+            NCNetworking.shared.uploadRequest.removeValue(forKey: fileNameLocalPath)
+            NCNetworking.shared.downloadRequest.removeValue(forKey: fileNameLocalPath)
+            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
+            return
+        }
+
+        // DOWNLOAD
+        if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload {
+            if let request = NCNetworking.shared.downloadRequest[fileNameLocalPath] {
+                request.cancel()
+            } else if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) {
+                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                           session: "",
+                                                           sessionError: "",
+                                                           selector: "",
+                                                           status: NCGlobal.shared.metadataStatusNormal)
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
+                                                object: nil,
+                                                userInfo: ["ocId": metadata.ocId,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "account": metadata.account])
+            }
+            return
+        }
+
+        // UPLOAD FOREGROUND
+        if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload {
+            if let request = NCNetworking.shared.uploadRequest[fileNameLocalPath] {
+                request.cancel()
+                NCNetworking.shared.uploadRequest.removeValue(forKey: fileNameLocalPath)
+            }
+            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
+                                            object: nil,
+                                            userInfo: ["ocId": metadata.ocId,
+                                                       "serverUrl": metadata.serverUrl,
+                                                       "account": metadata.account])
+            return
+        }
+
+        // UPLOAD BACKGROUND
+        var session: URLSession?
+        if metadata.session == NCNetworking.shared.sessionUploadBackground {
+            session = NCNetworking.shared.sessionManagerUploadBackground
+        } else if metadata.session == NCNetworking.shared.sessionUploadBackgroundWWan {
+            session = NCNetworking.shared.sessionManagerUploadBackgroundWWan
+        }
+        if let tasks = await session?.tasks {
+            for task in tasks.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
+                if task.taskIdentifier == metadata.sessionTaskIdentifier {
+                    task.cancel()
+                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                    NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
+                                                    object: nil,
+                                                    userInfo: ["ocId": metadata.ocId,
+                                                               "serverUrl": metadata.serverUrl,
+                                                               "account": metadata.account])
+                }
+            }
+        }
+    }
 }
 
 // MARK: -

+ 160 - 0
iOSClient/Main/Collection Common/NCCollectionViewCommonSelectTabBar.swift

@@ -0,0 +1,160 @@
+//
+//  NCCollectionViewCommonSelectionTabBar.swift
+//  Nextcloud
+//
+//  Created by Milen on 01.02.24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+
+protocol NCCollectionViewCommonSelectTabBarDelegate: AnyObject {
+    func selectAll()
+    func delete(selectedMetadatas: [tableMetadata])
+    func move(selectedMetadatas: [tableMetadata])
+    func share(selectedMetadatas: [tableMetadata])
+    func saveAsAvailableOffline(selectedMetadatas: [tableMetadata], isAnyOffline: Bool)
+    func lock(selectedMetadatas: [tableMetadata], isAnyLocked: Bool)
+}
+
+class NCCollectionViewCommonSelectTabBar: NCSelectableViewTabBar, ObservableObject {
+    var tabBarController: UITabBarController?
+    var hostingController: UIViewController?
+    open weak var delegate: NCCollectionViewCommonSelectTabBarDelegate?
+
+    var selectedMetadatas: [tableMetadata] = []
+
+    @Published var isAnyOffline = false
+    @Published var canSetAsOffline = false
+    @Published var isAnyDirectory = false
+    @Published var isAllDirectory = false
+    @Published var isAnyLocked = false
+    @Published var canUnlock = true
+    @Published var enableLock = false
+    @Published var isSelectedEmpty = true
+
+    init(tabBarController: UITabBarController? = nil, delegate: NCCollectionViewCommonSelectTabBarDelegate? = nil) {
+        let rootView = NCCollectionViewCommonSelectTabBarView(tabBarSelect: self)
+        hostingController = UIHostingController(rootView: rootView)
+
+        self.tabBarController = tabBarController
+        self.delegate = delegate
+
+        guard let tabBarController, let hostingController else { return }
+
+        
+        tabBarController.view.addSubview(hostingController.view)
+
+        hostingController.view.frame = tabBarController.tabBar.frame
+        hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        hostingController.view.backgroundColor = .clear
+        hostingController.view.isHidden = true
+    }
+
+    func show() {
+        guard let tabBarController, let hostingController else { return }
+
+        tabBarController.tabBar.isHidden = true
+
+        if hostingController.view.isHidden {
+            hostingController.view.isHidden = false
+
+            hostingController.view.transform = .init(translationX: 0, y: hostingController.view.frame.height)
+
+            UIView.animate(withDuration: 0.2) {
+                hostingController.view.transform = .init(translationX: 0, y: 0)
+            }
+        }
+    }
+
+    func hide() {
+        guard let tabBarController, let hostingController else { return }
+
+        hostingController.view.isHidden = true
+        tabBarController.tabBar.isHidden = false
+    }
+}
+
+struct NCCollectionViewCommonSelectTabBarView: View {
+    @ObservedObject var tabBarSelect: NCCollectionViewCommonSelectTabBar
+    @Environment(\.verticalSizeClass) var sizeClass
+
+    var body: some View {
+        VStack {
+            Spacer().frame(height: sizeClass == .compact ? 5 : 10)
+
+            HStack {
+                Button {
+                    tabBarSelect.delegate?.share(selectedMetadatas: tabBarSelect.selectedMetadatas)
+                } label: {
+                    Image(systemName: "square.and.arrow.up")
+                        .imageScale(sizeClass == .compact ? .medium : .large)
+
+                }
+                .frame(maxWidth: .infinity)
+                .disabled(tabBarSelect.isSelectedEmpty || tabBarSelect.isAllDirectory)
+
+                Button {
+                    tabBarSelect.delegate?.move(selectedMetadatas: tabBarSelect.selectedMetadatas)
+                } label: {
+                    Image(systemName: "rectangle.portrait.and.arrow.right")
+                        .imageScale(sizeClass == .compact ? .medium : .large)
+                }
+                .frame(maxWidth: .infinity)
+                .disabled(tabBarSelect.isSelectedEmpty)
+
+                Button {
+                    tabBarSelect.delegate?.delete(selectedMetadatas: tabBarSelect.selectedMetadatas)
+                } label: {
+                    Image(systemName: "trash")
+                        .imageScale(sizeClass == .compact ? .medium : .large)
+                }
+                .tint(.red)
+                .frame(maxWidth: .infinity)
+                .disabled(tabBarSelect.isSelectedEmpty)
+
+                Menu {
+                    Button(action: {
+                        tabBarSelect.delegate?.saveAsAvailableOffline(selectedMetadatas: tabBarSelect.selectedMetadatas, isAnyOffline: tabBarSelect.isAnyOffline)
+                    }, label: {
+                        Label(NSLocalizedString(tabBarSelect.isAnyOffline ? "_remove_available_offline_" : "_set_available_offline_", comment: ""), systemImage: tabBarSelect.isAnyOffline ? "icloud.slash" : "icloud.and.arrow.down")
+
+                        if !tabBarSelect.canSetAsOffline && !tabBarSelect.isAnyOffline {
+                            Text(NSLocalizedString("_e2ee_set_as_offline_", comment: ""))
+                        }
+                    })
+                    .disabled(!tabBarSelect.isAnyOffline && (!tabBarSelect.canSetAsOffline || tabBarSelect.isSelectedEmpty))
+
+                    Button(action: {
+                        tabBarSelect.delegate?.lock(selectedMetadatas: tabBarSelect.selectedMetadatas, isAnyLocked: tabBarSelect.isAnyLocked)
+                    }, label: {
+                        Label(NSLocalizedString(tabBarSelect.isAnyLocked ? "_unlock_" : "_lock_", comment: ""), systemImage: tabBarSelect.isAnyLocked ? "lock.open" : "lock")
+
+                        if !tabBarSelect.enableLock {
+                            Text(NSLocalizedString("_lock_no_permissions_selected_", comment: ""))
+                        }
+                    })
+                    .disabled(!tabBarSelect.enableLock || tabBarSelect.isSelectedEmpty)
+
+                    Button(action: {
+                        tabBarSelect.delegate?.selectAll()
+                    }, label: {
+                        Label(NSLocalizedString("_select_all_", comment: ""), systemImage: "checkmark")
+                    })
+                } label: {
+                    Image(systemName: "ellipsis.circle")
+                        .imageScale(sizeClass == .compact ? .medium : .large)
+                }
+                .frame(maxWidth: .infinity)
+            }
+        }
+        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+        .background(.thinMaterial)
+        .overlay(Rectangle().frame(width: nil, height: 0.5, alignment: .top).foregroundColor(Color(UIColor.separator)), alignment: .top)
+    }
+}
+
+#Preview {
+    NCCollectionViewCommonSelectTabBarView(tabBarSelect: NCCollectionViewCommonSelectTabBar())
+}

+ 29 - 90
iOSClient/Main/Collection Common/NCSelectableNavigationView.swift

@@ -33,8 +33,13 @@ extension RealmSwiftObject {
     }
 }
 
-protocol NCSelectableNavigationView: AnyObject {
+public protocol NCSelectableViewTabBar {
+    var tabBarController: UITabBarController? { get }
+    var hostingController: UIViewController? { get }
+}
 
+protocol NCSelectableNavigationView: AnyObject {
+    var viewController: UIViewController { get }
     var appDelegate: AppDelegate { get }
     var selectableDataSource: [RealmSwiftObject] { get }
     var collectionView: UICollectionView! { get set }
@@ -45,47 +50,45 @@ protocol NCSelectableNavigationView: AnyObject {
     var navigationItem: UINavigationItem { get }
     var navigationController: UINavigationController? { get }
     var layoutKey: String { get }
-    var selectActions: [NCMenuAction] { get }
+    var serverUrl: String { get }
+    var tabBarSelect: NCSelectableViewTabBar? { get set }
 
     func reloadDataSource(withQueryDB: Bool)
     func setNavigationItems()
+    func setNavigationRightItems()
+    func createMenuActions() -> [UIMenuElement]
 
-    func tapSelectMenu()
-    func tapSelect()
+    func toggleSelect()
+    func onListSelected()
+    func onGridSelected()
 }
 
 extension NCSelectableNavigationView {
-
     func setNavigationItems() {
         setNavigationRightItems()
     }
 
-    func setNavigationRightItems() {
-        if isEditMode {
-            let more = UIBarButtonItem(image: UIImage(systemName: "ellipsis"), style: .plain, action: tapSelectMenu)
-            navigationItem.rightBarButtonItems = [more]
-        } else {
-            let select = UIBarButtonItem(title: NSLocalizedString("_select_", comment: ""), style: UIBarButtonItem.Style.plain, action: tapSelect)
-            let notification = UIBarButtonItem(image: UIImage(systemName: "bell"), style: .plain, action: tapNotification)
-            if layoutKey == NCGlobal.shared.layoutViewFiles {
-                navigationItem.rightBarButtonItems = [select, notification]
-            } else {
-                navigationItem.rightBarButtonItems = [select]
-            }
-        }
+    func saveLayout(_ layoutForView: NCDBLayoutForView) {
+        NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView)
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
+
+        setNavigationRightItems()
     }
 
-    func tapSelect() {
-        isEditMode = !isEditMode
-        selectOcId.removeAll()
-        selectIndexPath.removeAll()
-        self.setNavigationItems()
-        self.collectionView.reloadData()
+    func toggleSelect() {
+        DispatchQueue.main.async {
+            self.isEditMode = !self.isEditMode
+            self.selectOcId.removeAll()
+            self.selectIndexPath.removeAll()
+            self.setNavigationItems()
+            self.collectionView.reloadData()
+        }
     }
 
     func collectionViewSelectAll() {
         selectOcId = selectableDataSource.compactMap({ $0.primaryKeyValue })
         collectionView.reloadData()
+        self.setNavigationRightItems()
     }
 
     func tapNotification() {
@@ -96,71 +99,7 @@ extension NCSelectableNavigationView {
 }
 
 extension NCSelectableNavigationView where Self: UIViewController {
-    func tapSelectMenu() {
-        presentMenu(with: selectActions)
-    }
-
-    var selectActions: [NCMenuAction] {
-        var actions = [NCMenuAction]()
-
-        actions.append(.cancelAction {
-            self.tapSelect()
-        })
-        if selectOcId.count != selectableDataSource.count {
-            actions.append(.selectAllAction(action: collectionViewSelectAll))
-        }
-
-        guard !selectOcId.isEmpty else { return actions }
-
-        actions.append(.seperator(order: 0))
-
-        var selectedMetadatas: [tableMetadata] = []
-        var selectedMediaMetadatas: [tableMetadata] = []
-        var isAnyOffline = false
-        var isAnyFolder = false
-        var isAnyLocked = false
-        var canUnlock = true
-
-        for ocId in selectOcId {
-            guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { continue }
-            selectedMetadatas.append(metadata)
-            if [NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue].contains(metadata.classFile) {
-                selectedMediaMetadatas.append(metadata)
-            }
-            if metadata.directory { isAnyFolder = true }
-            if metadata.lock {
-                isAnyLocked = true
-                if metadata.lockOwner != appDelegate.userId {
-                    canUnlock = false
-                }
-            }
-
-            guard !isAnyOffline else { continue }
-            if metadata.directory,
-               let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", appDelegate.account, metadata.serverUrl + "/" + metadata.fileName)) {
-                isAnyOffline = directory.offline
-            } else if let localFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) {
-                isAnyOffline = localFile.offline
-            } // else: file is not offline, continue
-        }
-
-        actions.append(.openInAction(selectedMetadatas: selectedMetadatas, viewController: self, completion: tapSelect))
-
-        if !isAnyFolder, canUnlock, !NCGlobal.shared.capabilityFilesLockVersion.isEmpty {
-            actions.append(.lockUnlockFiles(shouldLock: !isAnyLocked, metadatas: selectedMetadatas, completion: tapSelect))
-        }
-
-        if !selectedMediaMetadatas.isEmpty {
-            actions.append(.saveMediaAction(selectedMediaMetadatas: selectedMediaMetadatas, completion: tapSelect))
-        }
-        actions.append(.setAvailableOfflineAction(selectedMetadatas: selectedMetadatas, isAnyOffline: isAnyOffline, viewController: self, completion: {
-            self.reloadDataSource(withQueryDB: true)
-            self.tapSelect()
-        }))
-
-        actions.append(.moveOrCopyAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, completion: tapSelect))
-        actions.append(.copyAction(selectOcId: selectOcId, completion: tapSelect))
-        actions.append(.deleteAction(selectedMetadatas: selectedMetadatas, indexPath: selectIndexPath, viewController: self, completion: tapSelect))
-        return actions
+    var viewController: UIViewController {
+        self
     }
 }

+ 0 - 112
iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.storyboard

@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="hTm-e0-ORl">
-    <device id="retina4_7" orientation="portrait" appearance="light"/>
-    <dependencies>
-        <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
-        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
-        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
-    </dependencies>
-    <scenes>
-        <!--Create Form Upload Voice Note-->
-        <scene sceneID="UDa-5t-Os6">
-            <objects>
-                <viewController storyboardIdentifier="NCCreateFormUploadRichdocuments.storyboard" id="uQo-FX-ejX" customClass="NCCreateFormUploadVoiceNote" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
-                    <view key="view" contentMode="scaleToFill" id="PyZ-sH-zNM">
-                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <subviews>
-                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" scrollEnabled="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="eeP-9N-ZRP">
-                                <rect key="frame" x="0.0" y="163" width="375" height="504"/>
-                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                <connections>
-                                    <outlet property="dataSource" destination="uQo-FX-ejX" id="Y7U-AM-3WZ"/>
-                                    <outlet property="delegate" destination="uQo-FX-ejX" id="gsE-cc-f9G"/>
-                                </connections>
-                            </tableView>
-                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="s98-hk-uUP">
-                                <rect key="frame" x="150" y="54" width="75" height="75"/>
-                                <constraints>
-                                    <constraint firstAttribute="width" constant="75" id="h48-gg-iPB"/>
-                                    <constraint firstAttribute="height" constant="75" id="mjB-VI-Gzf"/>
-                                </constraints>
-                                <state key="normal" image="audioPlay"/>
-                                <connections>
-                                    <action selector="playStop:" destination="uQo-FX-ejX" eventType="touchUpInside" id="4AN-dB-Qq8"/>
-                                </connections>
-                            </button>
-                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="we1-Q7-8Us">
-                                <rect key="frame" x="20" y="132.5" width="70" height="17"/>
-                                <constraints>
-                                    <constraint firstAttribute="width" constant="70" id="Su3-nN-I5z"/>
-                                </constraints>
-                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
-                                <nil key="textColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
-                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zd0-7e-z9V" userLabel="Label Duration">
-                                <rect key="frame" x="285" y="132.5" width="70" height="17"/>
-                                <constraints>
-                                    <constraint firstAttribute="width" constant="70" id="JZa-Lm-wgx"/>
-                                </constraints>
-                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
-                                <nil key="textColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
-                            <progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="6HO-NK-obf">
-                                <rect key="frame" x="100" y="139" width="175" height="4"/>
-                            </progressView>
-                        </subviews>
-                        <viewLayoutGuide key="safeArea" id="ILQ-5j-b92"/>
-                        <constraints>
-                            <constraint firstItem="s98-hk-uUP" firstAttribute="top" secondItem="ILQ-5j-b92" secondAttribute="top" constant="10" id="5mu-uh-SqU"/>
-                            <constraint firstItem="6HO-NK-obf" firstAttribute="leading" secondItem="we1-Q7-8Us" secondAttribute="trailing" constant="10" id="C6M-oc-T1Z"/>
-                            <constraint firstItem="6HO-NK-obf" firstAttribute="centerY" secondItem="we1-Q7-8Us" secondAttribute="centerY" id="J28-Wh-Byv"/>
-                            <constraint firstItem="6HO-NK-obf" firstAttribute="top" secondItem="s98-hk-uUP" secondAttribute="bottom" constant="10" id="RxM-5b-ZEm"/>
-                            <constraint firstItem="zd0-7e-z9V" firstAttribute="centerY" secondItem="6HO-NK-obf" secondAttribute="centerY" id="T4r-QI-wSb"/>
-                            <constraint firstItem="ILQ-5j-b92" firstAttribute="trailing" secondItem="zd0-7e-z9V" secondAttribute="trailing" constant="20" id="Zkq-S4-Hnu"/>
-                            <constraint firstItem="ILQ-5j-b92" firstAttribute="bottom" secondItem="eeP-9N-ZRP" secondAttribute="bottom" id="a74-Md-bui"/>
-                            <constraint firstItem="zd0-7e-z9V" firstAttribute="leading" secondItem="6HO-NK-obf" secondAttribute="trailing" constant="10" id="bKe-sM-Gvy"/>
-                            <constraint firstItem="eeP-9N-ZRP" firstAttribute="top" secondItem="6HO-NK-obf" secondAttribute="bottom" constant="20" id="eqV-Ls-16q"/>
-                            <constraint firstItem="eeP-9N-ZRP" firstAttribute="leading" secondItem="ILQ-5j-b92" secondAttribute="leading" id="pNx-zH-54E"/>
-                            <constraint firstItem="we1-Q7-8Us" firstAttribute="leading" secondItem="ILQ-5j-b92" secondAttribute="leading" constant="20" id="uvM-m5-Ofp"/>
-                            <constraint firstItem="s98-hk-uUP" firstAttribute="centerX" secondItem="ILQ-5j-b92" secondAttribute="centerX" id="vLn-iu-xOz"/>
-                            <constraint firstItem="ILQ-5j-b92" firstAttribute="trailing" secondItem="eeP-9N-ZRP" secondAttribute="trailing" id="yxz-bK-MTp"/>
-                        </constraints>
-                    </view>
-                    <navigationItem key="navigationItem" id="YB9-Lg-X9d"/>
-                    <connections>
-                        <outlet property="buttonPlayStop" destination="s98-hk-uUP" id="Aek-KA-loq"/>
-                        <outlet property="labelDuration" destination="zd0-7e-z9V" id="GQW-RU-pdA"/>
-                        <outlet property="labelTimer" destination="we1-Q7-8Us" id="9LN-EV-8tm"/>
-                        <outlet property="progressView" destination="6HO-NK-obf" id="ZwP-0h-8Df"/>
-                        <outlet property="tableView" destination="eeP-9N-ZRP" id="dzG-bV-m9u"/>
-                    </connections>
-                </viewController>
-                <placeholder placeholderIdentifier="IBFirstResponder" id="tPT-Fr-1CS" userLabel="First Responder" sceneMemberID="firstResponder"/>
-            </objects>
-            <point key="canvasLocation" x="1196" y="228.93553223388307"/>
-        </scene>
-        <!--Navigation Controller-->
-        <scene sceneID="3OT-UN-0Dc">
-            <objects>
-                <navigationController automaticallyAdjustsScrollViewInsets="NO" id="hTm-e0-ORl" sceneMemberID="viewController">
-                    <toolbarItems/>
-                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="erz-WY-qOP">
-                        <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
-                        <autoresizingMask key="autoresizingMask"/>
-                    </navigationBar>
-                    <nil name="viewControllers"/>
-                    <connections>
-                        <segue destination="uQo-FX-ejX" kind="relationship" relationship="rootViewController" id="6dF-7E-8yd"/>
-                    </connections>
-                </navigationController>
-                <placeholder placeholderIdentifier="IBFirstResponder" id="GZJ-Ut-ON6" userLabel="First Responder" sceneMemberID="firstResponder"/>
-            </objects>
-            <point key="canvasLocation" x="256.80000000000001" y="228.93553223388307"/>
-        </scene>
-    </scenes>
-    <resources>
-        <image name="audioPlay" width="300" height="300"/>
-    </resources>
-</document>

+ 0 - 347
iOSClient/Main/Create cloud/NCCreateFormUploadVoiceNote.swift

@@ -1,347 +0,0 @@
-//
-//  NCCreateFormUploadVoiceNote.swift
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 9/03/2019.
-//  Copyright © 2019 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-import UIKit
-import NextcloudKit
-
-class NCCreateFormUploadVoiceNote: XLFormViewController, NCSelectDelegate, AVAudioPlayerDelegate, NCCreateFormUploadConflictDelegate {
-
-    @IBOutlet weak var buttonPlayStop: UIButton!
-    @IBOutlet weak var labelTimer: UILabel!
-    @IBOutlet weak var labelDuration: UILabel!
-    @IBOutlet weak var progressView: UIProgressView!
-
-    private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
-    let utilityFileSystem = NCUtilityFileSystem()
-    let utility = NCUtility()
-    private var serverUrl = ""
-    private var titleServerUrl = ""
-    private var fileName = ""
-    private var fileNamePath = ""
-    private var durationPlayer: TimeInterval = 0
-    private var counterSecondPlayer: TimeInterval = 0
-
-    private var audioPlayer: AVAudioPlayer!
-    private var timer = Timer()
-
-    var cellBackgoundColor = UIColor.secondarySystemGroupedBackground
-
-    // MARK: - View Life Cycle
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(cancel))
-        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_save_", comment: ""), style: UIBarButtonItem.Style.plain, target: self, action: #selector(save))
-
-        self.tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
-
-        view.backgroundColor = .systemGroupedBackground
-        tableView.backgroundColor = .systemGroupedBackground
-        cellBackgoundColor = .secondarySystemGroupedBackground
-
-        self.title = NSLocalizedString("_voice_memo_title_", comment: "")
-
-        // Button Play Stop
-        buttonPlayStop.setImage(UIImage(named: "audioPlay")!.image(color: UIColor.systemGray, size: 100), for: .normal)
-
-        // Progress view
-        progressView.progress = 0
-        progressView.progressTintColor = .green
-        progressView.trackTintColor = UIColor(red: 247.0 / 255.0, green: 247.0 / 255.0, blue: 247.0 / 255.0, alpha: 1.0)
-
-        labelTimer.textColor = .label
-        labelDuration.textColor = .label
-
-        initializeForm()
-    }
-
-    override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-
-        updateTimerUI()
-    }
-
-    override func viewDidDisappear(_ animated: Bool) {
-        super.viewDidDisappear(animated)
-
-        if audioPlayer.isPlaying {
-            stop()
-        }
-    }
-
-    public func setup(serverUrl: String, fileNamePath: String, fileName: String) {
-
-        if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
-            titleServerUrl = "/"
-        } else {
-            titleServerUrl = (serverUrl as NSString).lastPathComponent
-        }
-
-        self.fileName = fileName
-        self.serverUrl = serverUrl
-        self.fileNamePath = fileNamePath
-
-        // player
-        do {
-            try audioPlayer = AVAudioPlayer(contentsOf: URL(fileURLWithPath: fileNamePath))
-            audioPlayer.prepareToPlay()
-            audioPlayer.delegate = self
-            durationPlayer = TimeInterval(audioPlayer.duration)
-        } catch {
-            buttonPlayStop.isEnabled = false
-        }
-    }
-
-    // MARK: XLForm
-
-    func initializeForm() {
-
-        let form: XLFormDescriptor = XLFormDescriptor() as XLFormDescriptor
-        form.rowNavigationOptions = XLFormRowNavigationOptions.stopDisableRow
-
-        var section: XLFormSectionDescriptor
-        var row: XLFormRowDescriptor
-
-        // Section: Destination Folder
-
-        section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_save_path_", comment: "").uppercased())
-        form.addFormSection(section)
-
-        row = XLFormRowDescriptor(tag: "ButtonDestinationFolder", rowType: XLFormRowDescriptorTypeButton, title: self.titleServerUrl)
-        row.action.formSelector = #selector(changeDestinationFolder(_:))
-        row.cellConfig["backgroundColor"] = cellBackgoundColor
-
-        row.cellConfig["imageView.image"] = UIImage(named: "folder")!.image(color: NCBrandColor.shared.brandElement, size: 25)
-
-        row.cellConfig["textLabel.textAlignment"] = NSTextAlignment.right.rawValue
-        row.cellConfig["textLabel.font"] = UIFont.systemFont(ofSize: 15.0)
-        row.cellConfig["textLabel.textColor"] = UIColor.label
-
-        section.addFormRow(row)
-
-        // Section: File Name
-
-        section = XLFormSectionDescriptor.formSection(withTitle: NSLocalizedString("_filename_", comment: "").uppercased())
-        form.addFormSection(section)
-
-        row = XLFormRowDescriptor(tag: "fileName", rowType: XLFormRowDescriptorTypeText, title: NSLocalizedString("_filename_", comment: ""))
-        row.value = self.fileName
-        row.cellConfig["backgroundColor"] = cellBackgoundColor
-
-        row.cellConfig["textLabel.font"] = UIFont.systemFont(ofSize: 15.0)
-        row.cellConfig["textLabel.textColor"] = UIColor.label
-
-        row.cellConfig["textField.textAlignment"] = NSTextAlignment.right.rawValue
-        row.cellConfig["textField.font"] = UIFont.systemFont(ofSize: 15.0)
-        row.cellConfig["textField.textColor"] = UIColor.label
-
-        section.addFormRow(row)
-
-        self.form = form
-    }
-
-    override func formRowDescriptorValueHasChanged(_ formRow: XLFormRowDescriptor!, oldValue: Any!, newValue: Any!) {
-
-        super.formRowDescriptorValueHasChanged(formRow, oldValue: oldValue, newValue: newValue)
-
-        if formRow.tag == "fileName" {
-
-            self.form.delegate = nil
-
-            if let fileNameNew = formRow.value as? String {
-                self.fileName = utility.removeForbiddenCharacters(fileNameNew)
-            }
-
-            formRow.value = self.fileName
-            self.updateFormRow(formRow)
-
-            self.form.delegate = self
-        }
-    }
-
-    // MARK: TableViewDelegate
-
-    override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
-        let header = view as? UITableViewHeaderFooterView
-        header?.textLabel?.font = UIFont.systemFont(ofSize: 13.0)
-        header?.textLabel?.textColor = .gray
-        header?.tintColor = cellBackgoundColor
-    }
-
-    // MARK: - Action
-
-    func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) {
-
-        if serverUrl != nil {
-
-            self.serverUrl = serverUrl!
-
-            if serverUrl == utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) {
-                self.titleServerUrl = "/"
-            } else {
-                self.titleServerUrl = (serverUrl! as NSString).lastPathComponent
-            }
-
-            // Update
-            let row: XLFormRowDescriptor = self.form.formRow(withTag: "ButtonDestinationFolder")!
-            row.title = self.titleServerUrl
-            self.updateFormRow(row)
-        }
-    }
-
-    @objc func save() {
-
-        let rowFileName: XLFormRowDescriptor = self.form.formRow(withTag: "fileName")!
-        guard let name = rowFileName.value as? String else { return }
-        let ext = (name as NSString).pathExtension.uppercased()
-        var fileNameSave = ""
-
-        if ext.isEmpty {
-            fileNameSave = name + ".m4a"
-        } else {
-            fileNameSave = (name as NSString).deletingPathExtension + ".m4a"
-        }
-
-        let metadataForUpload = NCManageDatabase.shared.createMetadata(account: self.appDelegate.account, user: self.appDelegate.user, userId: self.appDelegate.userId, fileName: fileNameSave, fileNameView: fileNameSave, ocId: UUID().uuidString, serverUrl: self.serverUrl, urlBase: self.appDelegate.urlBase, url: "", contentType: "")
-
-        metadataForUpload.session = NCNetworking.shared.sessionUploadBackground
-        metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
-        metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
-        metadataForUpload.size = utilityFileSystem.getFileSize(filePath: fileNamePath)
-
-        if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: fileNameSave) != nil {
-
-            guard let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict else { return }
-
-            conflict.textLabelDetailNewFile = NSLocalizedString("_now_", comment: "")
-            conflict.serverUrl = serverUrl
-            conflict.metadatasUploadInConflict = [metadataForUpload]
-            conflict.delegate = self
-
-            self.present(conflict, animated: true, completion: nil)
-
-        } else {
-
-            dismissAndUpload(metadataForUpload)
-        }
-    }
-
-    func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) {
-
-        if let metadatas {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
-                self.dismissAndUpload(metadatas[0])
-            }
-        }
-    }
-
-    func dismissAndUpload(_ metadata: tableMetadata) {
-
-        utilityFileSystem.copyFile(atPath: self.fileNamePath, toPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
-
-        NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: [metadata], completion: { _ in })
-
-        self.dismiss(animated: true, completion: nil)
-    }
-
-    @objc func cancel() {
-
-        try? FileManager.default.removeItem(atPath: fileNamePath)
-        self.dismiss(animated: true, completion: nil)
-    }
-
-    @objc func changeDestinationFolder(_ sender: XLFormRowDescriptor) {
-
-        self.deselectFormRow(sender)
-
-        let storyboard = UIStoryboard(name: "NCSelect", bundle: nil)
-        if let navigationController = storyboard.instantiateInitialViewController() as? UINavigationController,
-           let viewController = navigationController.topViewController as? NCSelect {
-
-            viewController.delegate = self
-            viewController.typeOfCommandView = .selectCreateFolder
-            viewController.includeDirectoryE2EEncryption = true
-
-            self.present(navigationController, animated: true, completion: nil)
-        }
-    }
-
-    // MARK: Player - Timer
-
-    func updateTimerUI() {
-        labelTimer.text = String().formatSecondsToString(counterSecondPlayer)
-        labelDuration.text = String().formatSecondsToString(durationPlayer)
-        progressView.progress = Float(counterSecondPlayer / durationPlayer)
-    }
-
-    @objc func updateTimer() {
-        counterSecondPlayer += 1
-        updateTimerUI()
-    }
-
-    @IBAction func playStop(_ sender: Any) {
-
-        if audioPlayer.isPlaying {
-
-            stop()
-
-        } else {
-
-            start()
-        }
-    }
-
-    func start() {
-
-        audioPlayer.prepareToPlay()
-        audioPlayer.play()
-
-        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
-
-        buttonPlayStop.setImage(UIImage(named: "stop")!.image(color: UIColor.systemGray, size: 100), for: .normal)
-    }
-
-    func stop() {
-
-        audioPlayer.currentTime = 0.0
-        audioPlayer.stop()
-
-        timer.invalidate()
-        counterSecondPlayer = 0
-        progressView.progress = 0
-        updateTimerUI()
-
-        buttonPlayStop.setImage(UIImage(named: "audioPlay")!.image(color: UIColor.systemGray, size: 100), for: .normal)
-    }
-
-    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
-
-        timer.invalidate()
-        counterSecondPlayer = 0
-        progressView.progress = 0
-        updateTimerUI()
-
-        buttonPlayStop.setImage(UIImage(named: "audioPlay")!.image(color: UIColor.systemGray, size: 100), for: .normal)
-    }
-}

+ 0 - 165
iOSClient/Main/Create cloud/NCCreateMenuAdd.swift

@@ -1,165 +0,0 @@
-//
-//  NCCreateMenuAdd.swift
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 14/11/2018.
-//  Copyright © 2018 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-import Foundation
-import Sheeeeeeeeet
-
-class NCCreateMenuAdd: NSObject {
-
-    // swiftlint:disable force_cast
-    weak var appDelegate = UIApplication.shared.delegate as! AppDelegate
-    // swiftlint:enable force_cast
-
-    var isNextcloudTextAvailable = false
-
-    @objc init(viewController: UIViewController, view: UIView) {
-        super.init()
-
-        if self.appDelegate.reachability.isReachable() && NCBrandBeta.shared.directEditing && NCManageDatabase.sharedInstance.getDirectEditingCreators(account: self.appDelegate.activeAccount) != nil {
-            isNextcloudTextAvailable = true
-        }
-
-        var items = [MenuItem]()
-
-        ActionSheetTableView.appearance().backgroundColor = NCBrandColor.sharedInstance.backgroundForm
-        ActionSheetTableView.appearance().separatorColor = NCBrandColor.sharedInstance.separator
-        ActionSheetItemCell.appearance().backgroundColor = NCBrandColor.sharedInstance.backgroundForm
-        ActionSheetItemCell.appearance().titleColor = NCBrandColor.sharedInstance.textView
-
-        items.append(MenuItem(title: NSLocalizedString("_upload_photos_videos_", comment: ""), value: 10, image: CCGraphics.changeThemingColorImage(UIImage(named: "file_photo"), width: 50, height: 50, color: NCBrandColor.sharedInstance.icon)))
-
-        items.append(MenuItem(title: NSLocalizedString("_upload_file_", comment: ""), value: 20, image: CCGraphics.changeThemingColorImage(UIImage(named: "file"), width: 50, height: 50, color: NCBrandColor.sharedInstance.icon)))
-
-        if NCBrandOptions.sharedInstance.use_imi_viewer {
-            items.append(MenuItem(title: NSLocalizedString("_im_create_new_file", tableName: "IMLocalizable", bundle: Bundle.main, value: "", comment: ""), value: 21, image: CCGraphics.scale(UIImage(named: "imagemeter"), to: CGSize(width: 25, height: 25), isAspectRation: true)))
-        }
-
-        if isNextcloudTextAvailable {
-            items.append(MenuItem(title: NSLocalizedString("_create_nextcloudtext_document_", comment: ""), value: 31, image: CCGraphics.changeThemingColorImage(UIImage(named: "file_txt"), width: 50, height: 50, color: NCBrandColor.sharedInstance.icon)))
-        } else {
-            items.append(MenuItem(title: NSLocalizedString("_upload_file_text_", comment: ""), value: 30, image: CCGraphics.changeThemingColorImage(UIImage(named: "file_txt"), width: 50, height: 50, color: NCBrandColor.sharedInstance.icon)))
-        }
-
-#if !targetEnvironment(simulator)
-        if #available(iOS 11.0, *) {
-            items.append(MenuItem(title: NSLocalizedString("_scans_document_", comment: ""), value: 40, image: CCGraphics.changeThemingColorImage(UIImage(named: "scan"), width: 50, height: 50, color: NCBrandColor.sharedInstance.icon)))
-        }
-#endif
-
-        items.append(MenuItem(title: NSLocalizedString("_create_voice_memo_", comment: ""), value: 50, image: CCGraphics.changeThemingColorImage(UIImage(named: "microphone"), width: 50, height: 50, color: NCBrandColor.sharedInstance.icon)))
-
-        items.append(MenuItem(title: NSLocalizedString("_create_folder_", comment: ""), value: 60, image: CCGraphics.changeThemingColorImage(UIImage(named: "folder"), width: 50, height: 50, color: NCBrandColor.sharedInstance.brandElement)))
-
-        if let richdocumentsMimetypes = NCManageDatabase.sharedInstance.getRichdocumentsMimetypes(account: appDelegate.activeAccount) {
-            if !richdocumentsMimetypes.isEmpty {
-                items.append(MenuItem(title: NSLocalizedString("_create_new_document_", comment: ""), value: 70, image: UIImage(named: "create_file_document")))
-                items.append(MenuItem(title: NSLocalizedString("_create_new_spreadsheet_", comment: ""), value: 80, image: UIImage(named: "create_file_xls")))
-                items.append(MenuItem(title: NSLocalizedString("_create_new_presentation_", comment: ""), value: 90, image: UIImage(named: "create_file_ppt")))
-            }
-        }
-
-        items.append(CancelButton(title: NSLocalizedString("_cancel_", comment: "")))
-
-        let actionSheet = ActionSheet(menu: Menu(items: items), action: { _, item in
-
-            if item.value as? Int == 10 { self.appDelegate.activeMain.openAssetsPickerController() }
-            if item.value as? Int == 20 { self.appDelegate.activeMain.openImportDocumentPicker() }
-            if item.value as? Int == 21 {
-                _ = IMCreate(serverUrl: self.appDelegate.activeMain.serverUrl)
-            }
-            if item.value as? Int == 30 {
-                let storyboard = UIStoryboard(name: "NCText", bundle: nil)
-                let controller = storyboard.instantiateViewController(withIdentifier: "NCText")
-                controller.modalPresentationStyle = UIModalPresentationStyle.pageSheet
-                self.appDelegate.activeMain.present(controller, animated: true, completion: nil)
-            }
-            if item.value as? Int == 31 {
-                guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() else {
-                    return
-                }
-                navigationController.modalPresentationStyle = UIModalPresentationStyle.formSheet
-
-                let viewController = (navigationController as? UINavigationController)?.topViewController as? NCCreateFormUploadDocuments
-                viewController.typeTemplate = k_template_document
-                viewController.serverUrl = self.appDelegate.activeMain.serverUrl
-                viewController.titleForm = NSLocalizedString("_create_nextcloudtext_document_", comment: "")
-
-                self.appDelegate.window.rootViewController?.present(navigationController, animated: true, completion: nil)
-            }
-            if item.value as? Int == 40 {
-                if #available(iOS 11.0, *) {
-                    NCCreateScanDocument.sharedInstance.openScannerDocument(viewController: self.appDelegate.activeMain)
-                }
-            }
-
-            if item.value as? Int == 50 { NCMainCommon.sharedInstance.startAudioRecorder() }
-
-            if item.value as? Int == 60 { self.appDelegate.activeMain.createFolder() }
-
-            if item.value as? Int == 70 {
-                guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() else {
-                    return
-                }
-                navigationController.modalPresentationStyle = UIModalPresentationStyle.formSheet
-
-                let viewController = (navigationController as? UINavigationController).topViewController as? NCCreateFormUploadDocuments
-                viewController.typeTemplate = k_template_document
-                viewController.serverUrl = self.appDelegate.activeMain.serverUrl
-                viewController.titleForm = NSLocalizedString("_create_new_document_", comment: "")
-
-                self.appDelegate.window.rootViewController?.present(navigationController, animated: true, completion: nil)
-            }
-            if item.value as? Int == 80 {
-                guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() else {
-                    return
-                }
-                navigationController.modalPresentationStyle = UIModalPresentationStyle.formSheet
-
-                let viewController = (navigationController as? UINavigationController).topViewController as? NCCreateFormUploadDocuments
-                viewController.typeTemplate = k_template_spreadsheet
-                viewController.serverUrl = self.appDelegate.activeMain.serverUrl
-                viewController.titleForm = NSLocalizedString("_create_new_spreadsheet_", comment: "")
-
-                self.appDelegate.window.rootViewController?.present(navigationController, animated: true, completion: nil)
-            }
-            if item.value as? Int == 90 {
-                guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() else {
-                    return
-                }
-                navigationController.modalPresentationStyle = UIModalPresentationStyle.formSheet
-
-                let viewController = (navigationController as? UINavigationController).topViewController as? NCCreateFormUploadDocuments
-                viewController.typeTemplate = k_template_presentation
-                viewController.serverUrl = self.appDelegate.activeMain.serverUrl
-                viewController.titleForm = NSLocalizedString("_create_new_presentation_", comment: "")
-
-                self.appDelegate.window.rootViewController?.present(navigationController, animated: true, completion: nil)
-            }
-
-            if item is CancelButton { print("Cancel buttons has the value `true`") }
-        })
-
-        actionSheet.present(in: viewController, from: view)
-
-    }
-}

+ 8 - 12
iOSClient/Main/Create cloud/NCUploadAssets.swift

@@ -50,11 +50,11 @@ struct PreviewStore {
 }
 
 class NCUploadAssets: NSObject, ObservableObject, NCCreateFormUploadConflictDelegate {
-
     @Published var serverUrl: String
     @Published var assets: [TLPHAsset]
     @Published var userBaseUrl: NCUserBaseUrl
     @Published var dismiss = false
+    @Published var isHiddenSave = true
     @Published var isUseAutoUploadFolder: Bool = false
     @Published var isUseAutoUploadSubFolder: Bool = false
     @Published var previewStore: [PreviewStore] = []
@@ -74,13 +74,16 @@ class NCUploadAssets: NSObject, ObservableObject, NCCreateFormUploadConflictDele
 
     func loadImages() {
         var previewStore: [PreviewStore] = []
+        self.showHUD = true
         DispatchQueue.global().async {
             for asset in self.assets {
                 guard let image = asset.fullResolutionImage?.resizeImage(size: CGSize(width: 300, height: 300), isAspectRation: true), let localIdentifier = asset.phAsset?.localIdentifier else { continue }
                 previewStore.append(PreviewStore(id: localIdentifier, asset: asset, assetType: asset.type, fileName: "", image: image))
             }
             DispatchQueue.main.async {
+                self.showHUD = false
                 self.previewStore = previewStore
+                self.isHiddenSave = false
             }
         }
     }
@@ -116,7 +119,7 @@ class NCUploadAssets: NSObject, ObservableObject, NCCreateFormUploadConflictDele
 
         func createProcessUploads() {
             if !self.dismiss {
-                NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: metadatas, completion: { _ in
+                NCNetworkingProcess.shared.createProcessUploads(metadatas: metadatas, completion: { _ in
                     self.dismiss = true
                 })
             }
@@ -146,7 +149,6 @@ class NCUploadAssets: NSObject, ObservableObject, NCCreateFormUploadConflictDele
 // MARK: - View
 
 struct UploadAssetsView: View {
-
     @State private var fileName: String = NCKeychain().getFileNameMask(key: NCGlobal.shared.keyFileNameMask)
     @State private var isMaintainOriginalFilename: Bool = NCKeychain().getOriginalFileName(key: NCGlobal.shared.keyFileNameOriginal)
     @State private var isAddFilenametype: Bool = NCKeychain().getFileNameType(key: NCGlobal.shared.keyFileNameType)
@@ -163,7 +165,6 @@ struct UploadAssetsView: View {
     var gridItems: [GridItem] = [GridItem()]
 
     @ObservedObject var uploadAssets: NCUploadAssets
-
     @Environment(\.presentationMode) var presentationMode
 
     init(uploadAssets: NCUploadAssets) {
@@ -180,7 +181,6 @@ struct UploadAssetsView: View {
     }
 
     private func setFileNameMaskForPreview(fileName: String?) -> String {
-
         guard let asset = uploadAssets.assets.first?.phAsset else { return "" }
         var preview: String = ""
         let creationDate = asset.creationDate ?? Date()
@@ -266,6 +266,7 @@ struct UploadAssetsView: View {
             metadata.session = NCNetworking.shared.sessionUploadBackground
             metadata.sessionSelector = NCGlobal.shared.selectorUploadFile
             metadata.status = NCGlobal.shared.metadataStatusWaitUpload
+            metadata.sessionDate = Date()
 
             // Modified
             if let previewStore = uploadAssets.previewStore.first(where: { $0.id == asset.localIdentifier }), let data = previewStore.data {
@@ -297,15 +298,12 @@ struct UploadAssetsView: View {
     }
 
     private func presentedQuickLook(index: Int) {
-
         var image: UIImage?
-
         if let imageData = uploadAssets.previewStore[index].data {
             image = UIImage(data: imageData)
         } else if let imageFullResolution = uploadAssets.previewStore[index].asset.fullResolutionImage?.fixedOrientation() {
             image = imageFullResolution
         }
-
         if let image = image {
             if let data = image.jpegData(compressionQuality: 1) {
                 do {
@@ -328,7 +326,6 @@ struct UploadAssetsView: View {
 
     private func getOriginalFilenameForPreview() -> NSString {
         NCKeychain().setOriginalFileName(key: NCGlobal.shared.keyFileNameOriginal, value: isMaintainOriginalFilename)
-
         if let asset = uploadAssets.assets.first?.phAsset {
             return asset.originalFilename
         } else {
@@ -337,7 +334,6 @@ struct UploadAssetsView: View {
     }
 
     var body: some View {
-
         let utilityFileSystem = NCUtilityFileSystem()
 
         NavigationView {
@@ -408,7 +404,7 @@ struct UploadAssetsView: View {
                             }
                         }
                     }
-                    .redacted(reason: uploadAssets.previewStore.isEmpty ? .placeholder : [])
+                    // .redacted(reason: uploadAssets.previewStore.isEmpty ? .placeholder : [])
 
                     Section {
                         Toggle(isOn: $isMaintainOriginalFilename, label: {
@@ -512,6 +508,7 @@ struct UploadAssetsView: View {
                     .buttonStyle(ButtonRounded(disabled: uploadAssets.uploadInProgress))
                     .listRowBackground(Color(UIColor.systemGroupedBackground))
                     .disabled(uploadAssets.uploadInProgress)
+                    .hiddenConditionally(isHidden: uploadAssets.isHiddenSave)
                 }
                 .navigationTitle(NSLocalizedString("_upload_photos_videos_", comment: ""))
                 .navigationBarTitleDisplayMode(.inline)
@@ -546,7 +543,6 @@ struct UploadAssetsView: View {
     }
 
     struct ImageAsset: View {
-
         @ObservedObject var uploadAssets: NCUploadAssets
         @State var index: Int
 

+ 20 - 12
iOSClient/Main/NCActionCenter.swift

@@ -115,9 +115,6 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
                 }
             }
 
-        case NCGlobal.shared.selectorLoadOffline:
-            NCManageDatabase.shared.setLocalFile(ocId: metadata.ocId, offline: true)
-
         case NCGlobal.shared.selectorPrint:
             // waiting close menu
             // https://github.com/nextcloud/ios/issues/2278
@@ -150,18 +147,25 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
         if isOffline {
             if metadata.directory {
                 NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, offline: false, account: appDelegate.account)
+                if let metadatas = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND sessionSelector == %@ AND status == %d", metadata.account, serverUrl, NCGlobal.shared.selectorSynchronizationOffline, NCGlobal.shared.metadataStatusWaitDownload)) {
+                    NCManageDatabase.shared.clearMetadataSession(metadatas: metadatas)
+                }
             } else {
-                NCManageDatabase.shared.setLocalFile(ocId: metadata.ocId, offline: false)
+                NCManageDatabase.shared.setOffLocalFile(ocId: metadata.ocId)
             }
         } else if metadata.directory {
-            NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, offline: true, account: appDelegate.account)
-            NCNetworking.shared.synchronization(account: metadata.account, serverUrl: serverUrl, selector: NCGlobal.shared.selectorSynchronizationOffline)
+            NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, offline: true, account: metadata.account)
+            NCNetworking.shared.synchronization(account: metadata.account, serverUrl: serverUrl, add: true)
         } else {
-            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadOffline) else { return }
-            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
-            if let metadata = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadOffline) {
-                NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
+            var metadatasSynchronizationOffline: [tableMetadata] = []
+            metadatasSynchronizationOffline.append(metadata)
+            if let metadata = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+                metadatasSynchronizationOffline.append(metadata)
             }
+            NCManageDatabase.shared.addLocalFile(metadata: metadata, offline: true)
+            NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: metadatasSynchronizationOffline,
+                                                                      session: NCNetworking.shared.sessionDownloadBackground,
+                                                                      selector: NCGlobal.shared.selectorSynchronizationOffline)
         }
     }
 
@@ -331,7 +335,9 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
         let processor = ParallelWorker(n: 5, titleKey: "_downloading_", totalTasks: downloadMetadata.count, hudView: appDelegate.window?.rootViewController?.view)
         for (metadata, url) in downloadMetadata {
             processor.execute { completion in
-                guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: "") else { return completion() }
+                guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                               session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                               selector: "") else { return completion() }
                 NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
                 } progressHandler: { progress in
                     processor.hud?.progress = Float(progress.fractionCompleted)
@@ -478,7 +484,9 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
 
             for metadata in downloadMetadatas {
                 processor.execute { completion in
-                    guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: "") else { return completion() }
+                    guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                                   session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                                   selector: "") else { return completion() }
                     NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
                     } requestHandler: { _ in
                     } progressHandler: { progress in

+ 1 - 1
iOSClient/Main/NCMainTabBar.swift

@@ -94,7 +94,7 @@ class NCMainTabBar: UITabBar {
 
         blurView.layer.mask = maskLayer
 
-        var border = CALayer()
+        let border = CALayer()
         border.backgroundColor = UIColor.separator.cgColor
         border.frame = CGRect(x: 0, y: 0, width: blurView.frame.width, height: 0.5)
 

+ 244 - 0
iOSClient/Main/NCPasscode.swift

@@ -0,0 +1,244 @@
+//
+//  NCPasscode.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 13/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import LocalAuthentication
+import TOPasscodeViewController
+
+public protocol NCPasscodeDelegate: AnyObject {
+    func evaluatePolicy(_ passcodeViewController: TOPasscodeViewController, isCorrectCode: Bool)
+    func passcodeReset(_ passcodeViewController: TOPasscodeViewController)
+    func requestedAccount()
+}
+
+// optional func
+public extension NCPasscodeDelegate {
+    func evaluatePolicy(_ passcodeViewController: TOPasscodeViewController, isCorrectCode: Bool) {}
+    func passcodeReset() {}
+    func requestedAccount() {}
+}
+
+class NCPasscode: NSObject, TOPasscodeViewControllerDelegate {
+    public static let shared: NCPasscode = {
+        let instance = NCPasscode()
+        return instance
+    }()
+    var isPasscodeReset: Bool {
+        let passcodeCounterFailReset = NCKeychain().passcodeCounterFailReset
+        return NCKeychain().resetAppCounterFail && passcodeCounterFailReset >= NCBrandOptions.shared.resetAppPasscodeAttempts
+    }
+    var isPasscodeCounterFail: Bool {
+        let passcodeCounterFail = NCKeychain().passcodeCounterFail
+        return passcodeCounterFail > 0 && passcodeCounterFail.isMultiple(of: 3)
+    }
+    var isPasscodePresented: Bool {
+        return privacyProtectionWindow?.rootViewController?.presentedViewController is TOPasscodeViewController
+    }
+    var privacyProtectionWindow: UIWindow?
+    var passcodeViewController: TOPasscodeViewController!
+    var delegate: NCPasscodeDelegate?
+
+    func presentPasscode(viewController: UIViewController? = nil, delegate: NCPasscodeDelegate?, completion: @escaping () -> Void) {
+        var error: NSError?
+        var viewController = viewController
+        self.delegate = delegate
+        defer {
+            self.delegate?.requestedAccount()
+        }
+        guard NCKeychain().passcode != nil, NCKeychain().requestPasscodeAtStart else { return }
+
+#if !EXTENSION
+        let appDelegate = UIApplication.shared.delegate as? AppDelegate
+        let presentedViewController = appDelegate?.window?.rootViewController?.presentedViewController
+        guard !(presentedViewController is NCLoginNavigationController) else { return }
+        // Make sure we have a privacy window (in case it's not enabled) only for App
+        self.showPrivacyProtectionWindow()
+        // show passcode on top of privacy window only for App
+        viewController = self.privacyProtectionWindow?.rootViewController
+#endif
+
+        passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: false)
+        passcodeViewController.delegate = self
+        passcodeViewController.keypadButtonShowLettering = false
+        if NCKeychain().touchFaceID, LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
+            if error == nil {
+                if LAContext().biometryType == .faceID {
+                    passcodeViewController.biometryType = .faceID
+                } else if LAContext().biometryType == .touchID {
+                    passcodeViewController.biometryType = .touchID
+                }
+                passcodeViewController.allowBiometricValidation = true
+                passcodeViewController.automaticallyPromptForBiometricValidation = false
+            }
+        }
+        viewController?.present(passcodeViewController, animated: true, completion: {
+            self.openAlert(passcodeViewController: self.passcodeViewController)
+            completion()
+        })
+    }
+
+    func enableTouchFaceID() {
+        guard NCKeychain().touchFaceID,
+              NCKeychain().passcode != nil,
+              NCKeychain().requestPasscodeAtStart,
+              !isPasscodeCounterFail,
+              let passcodeViewController
+        else { return }
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+            LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, evaluateError in
+                if success {
+                    DispatchQueue.main.async {
+                        passcodeViewController.dismiss(animated: true) {
+                            NCKeychain().passcodeCounterFail = 0
+                            NCKeychain().passcodeCounterFailReset = 0
+                            self.hidePrivacyProtectionWindow()
+                            self.delegate?.evaluatePolicy(passcodeViewController, isCorrectCode: true)
+                            self.delegate?.requestedAccount()
+                        }
+                    }
+                } else {
+                    if let error = evaluateError {
+                        switch error._code {
+                        case LAError.userFallback.rawValue, LAError.authenticationFailed.rawValue:
+                            if LAContext().biometryType == .faceID {
+                                NCKeychain().passcodeCounterFail = 2
+                                NCKeychain().passcodeCounterFailReset += 2
+                            } else {
+                                NCKeychain().passcodeCounterFail = 3
+                                NCKeychain().passcodeCounterFailReset += 3
+                            }
+                            self.openAlert(passcodeViewController: passcodeViewController)
+                        case LAError.biometryLockout.rawValue:
+                            LAContext().evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: NSLocalizedString("_deviceOwnerAuthentication_", comment: ""), reply: { success, _ in
+                                if success {
+                                    DispatchQueue.main.async {
+                                        NCKeychain().passcodeCounterFail = 0
+                                        self.enableTouchFaceID()
+                                    }
+                                }
+                            })
+                        case LAError.userCancel.rawValue:
+                            NCKeychain().passcodeCounterFail += 1
+                            NCKeychain().passcodeCounterFailReset += 1
+                        default:
+                            break
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    func didInputCorrectPasscode(in passcodeViewController: TOPasscodeViewController) {
+        DispatchQueue.main.async {
+            passcodeViewController.dismiss(animated: true) {
+                NCKeychain().passcodeCounterFail = 0
+                NCKeychain().passcodeCounterFailReset = 0
+                self.hidePrivacyProtectionWindow()
+                self.delegate?.requestedAccount()
+            }
+        }
+    }
+
+    func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool {
+        if code == NCKeychain().passcode {
+            self.delegate?.evaluatePolicy(passcodeViewController, isCorrectCode: true)
+            return true
+        } else {
+            NCKeychain().passcodeCounterFail += 1
+            NCKeychain().passcodeCounterFailReset += 1
+            openAlert(passcodeViewController: passcodeViewController)
+            self.delegate?.evaluatePolicy(passcodeViewController, isCorrectCode: false)
+            return false
+        }
+    }
+
+    func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) {
+        enableTouchFaceID()
+    }
+
+    func openAlert(passcodeViewController: TOPasscodeViewController) {
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+
+            if self.isPasscodeReset {
+
+                passcodeViewController.setContentHidden(true, animated: true)
+
+                let alertController = UIAlertController(title: NSLocalizedString("_reset_wrong_passcode_", comment: ""), message: nil, preferredStyle: .alert)
+                passcodeViewController.present(alertController, animated: true, completion: { })
+                self.delegate?.passcodeReset()
+
+            } else if self.isPasscodeCounterFail {
+
+                passcodeViewController.setContentHidden(true, animated: true)
+
+                let alertController = UIAlertController(title: NSLocalizedString("_passcode_counter_fail_", comment: ""), message: nil, preferredStyle: .alert)
+                passcodeViewController.present(alertController, animated: true, completion: { })
+
+                var seconds = NCBrandOptions.shared.passcodeSecondsFail
+                _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
+                    alertController.message = "\(seconds) " + NSLocalizedString("_seconds_", comment: "")
+                    seconds -= 1
+                    if seconds < 0 {
+                        timer.invalidate()
+                        alertController.dismiss(animated: true)
+                        passcodeViewController.setContentHidden(false, animated: true)
+                        NCKeychain().passcodeCounterFail = 0
+                        self.enableTouchFaceID()
+                    }
+                }
+            }
+        }
+    }
+
+    // MARK: - Privacy Protection
+
+    func showPrivacyProtectionWindow() {
+        guard privacyProtectionWindow == nil else {
+            privacyProtectionWindow?.isHidden = false
+            return
+        }
+
+        privacyProtectionWindow = UIWindow(frame: UIScreen.main.bounds)
+
+        let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
+        let initialViewController = storyboard.instantiateInitialViewController()
+
+        self.privacyProtectionWindow?.rootViewController = initialViewController
+
+        privacyProtectionWindow?.windowLevel = .alert + 1
+        privacyProtectionWindow?.makeKeyAndVisible()
+    }
+
+    func hidePrivacyProtectionWindow() {
+        guard !(privacyProtectionWindow?.rootViewController?.presentedViewController is TOPasscodeViewController) else { return }
+        UIWindow.animate(withDuration: 0.25) {
+            self.privacyProtectionWindow?.alpha = 0
+        } completion: { _ in
+            self.privacyProtectionWindow?.isHidden = true
+            self.privacyProtectionWindow = nil
+        }
+    }
+}

+ 2 - 1
iOSClient/Main/NCPickerViewController.swift

@@ -174,6 +174,7 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
                 metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
                 metadataForUpload.size = utilityFileSystem.getFileSize(filePath: toPath)
                 metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
+                metadataForUpload.sessionDate = Date()
 
                 if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: fileName) != nil {
                     metadatasInConflict.append(metadataForUpload)
@@ -182,7 +183,7 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
                 }
             }
 
-            NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: metadatas, completion: { _ in })
+            NCNetworkingProcess.shared.createProcessUploads(metadatas: metadatas)
 
             if !metadatasInConflict.isEmpty {
                 if let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict {

+ 0 - 56
iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.swift

@@ -26,9 +26,6 @@ import MarkdownKit
 
 class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate {
 
-    @IBOutlet weak var buttonSwitch: UIButton!
-    @IBOutlet weak var buttonOrder: UIButton!
-    @IBOutlet weak var buttonMore: UIButton!
     @IBOutlet weak var buttonTransfer: UIButton!
     @IBOutlet weak var imageButtonTransfer: UIImageView!
     @IBOutlet weak var labelTransfer: UILabel!
@@ -37,14 +34,10 @@ class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate
     @IBOutlet weak var textViewRichWorkspace: UITextView!
     @IBOutlet weak var labelSection: UILabel!
     @IBOutlet weak var viewTransfer: UIView!
-    @IBOutlet weak var viewButtonsView: UIView!
-    @IBOutlet weak var viewSeparator: UIView!
     @IBOutlet weak var viewRichWorkspace: UIView!
     @IBOutlet weak var viewSection: UIView!
 
     @IBOutlet weak var viewTransferHeightConstraint: NSLayoutConstraint!
-    @IBOutlet weak var viewButtonsViewHeightConstraint: NSLayoutConstraint!
-    @IBOutlet weak var viewSeparatorHeightConstraint: NSLayoutConstraint!
     @IBOutlet weak var viewRichWorkspaceHeightConstraint: NSLayoutConstraint!
     @IBOutlet weak var viewSectionHeightConstraint: NSLayoutConstraint!
     @IBOutlet weak var transferSeparatorBottomHeightConstraint: NSLayoutConstraint!
@@ -61,12 +54,6 @@ class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate
 
         backgroundColor = .clear
 
-        buttonSwitch.setImage(UIImage(systemName: "list.bullet")!.image(color: .systemGray, size: 25), for: .normal)
-
-        buttonOrder.setTitle("", for: .normal)
-        buttonOrder.setTitleColor(.systemBlue, for: .normal)
-        buttonMore.setImage(UIImage(named: "more")!.image(color: .systemGray, size: 25), for: .normal)
-
         // Gradient
         gradient.startPoint = CGPoint(x: 0, y: 0.50)
         gradient.endPoint = CGPoint(x: 0, y: 1)
@@ -76,9 +63,6 @@ class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate
         tap.delegate = self
         viewRichWorkspace?.addGestureRecognizer(tap)
 
-        viewSeparatorHeightConstraint.constant = 0.5
-        viewSeparator.backgroundColor = .separator
-
         markdownParser = MarkdownParser(font: UIFont.systemFont(ofSize: 15), color: .label)
         markdownParser.header.font = UIFont.systemFont(ofSize: 25)
         if let richWorkspaceText = richWorkspaceText {
@@ -116,46 +100,6 @@ class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate
         setInterfaceColor()
     }
 
-    // MARK: - View
-
-    func setStatusButtonsView(enable: Bool) {
-
-        buttonSwitch.isEnabled = enable
-        buttonOrder.isEnabled = enable
-        buttonMore.isEnabled = enable
-    }
-
-    func buttonMoreIsHidden(_ isHidden: Bool) {
-
-        buttonMore.isHidden = isHidden
-    }
-
-    func setImageSwitchList() {
-
-        buttonSwitch.setImage(UIImage(systemName: "list.bullet")!.image(color: .systemGray, width: 20, height: 15), for: .normal)
-    }
-
-    func setImageSwitchGrid() {
-
-        buttonSwitch.setImage(UIImage(systemName: "square.grid.2x2")!.image(color: .systemGray, size: 20), for: .normal)
-    }
-
-    func setButtonsView(height: CGFloat) {
-
-        viewButtonsViewHeightConstraint.constant = height
-        if height == 0 {
-            viewButtonsView.isHidden = true
-        } else {
-            viewButtonsView.isHidden = false
-        }
-    }
-
-    func setSortedTitle(_ title: String) {
-
-        let title = NSLocalizedString(title, comment: "")
-        buttonOrder.setTitle(title, for: .normal)
-    }
-
     // MARK: - RichWorkspace
 
     func setRichWorkspaceHeight(_ size: CGFloat) {

+ 3 - 75
iOSClient/Main/Section Header Footer/NCSectionHeaderMenu.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina4_7" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -15,60 +15,6 @@
             <rect key="frame" x="0.0" y="0.0" width="574" height="438"/>
             <autoresizingMask key="autoresizingMask"/>
             <subviews>
-                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="s4I-Jo-yCE">
-                    <rect key="frame" x="0.0" y="20" width="574" height="50"/>
-                    <subviews>
-                        <button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1LD-cd-zhc">
-                            <rect key="frame" x="10" y="12.5" width="25" height="25"/>
-                            <constraints>
-                                <constraint firstAttribute="width" constant="25" id="D76-X9-Tw9"/>
-                                <constraint firstAttribute="height" constant="25" id="izT-Ru-XYG"/>
-                            </constraints>
-                            <state key="normal" image="switchList"/>
-                            <connections>
-                                <action selector="touchUpInsideSwitch:" destination="tys-A2-nDX" eventType="touchUpInside" id="iT8-1j-fib"/>
-                            </connections>
-                        </button>
-                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0bo-yl-t5k">
-                            <rect key="frame" x="45" y="11" width="163" height="28"/>
-                            <fontDescription key="fontDescription" type="system" pointSize="13"/>
-                            <state key="normal" title="Sort by name (from A to Z)">
-                                <color key="titleColor" systemColor="darkTextColor"/>
-                            </state>
-                            <connections>
-                                <action selector="touchUpInsideOrder:" destination="tys-A2-nDX" eventType="touchUpInside" id="oiL-3O-hMQ"/>
-                            </connections>
-                        </button>
-                        <button hidden="YES" opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="D0O-wK-14O">
-                            <rect key="frame" x="539" y="12.5" width="25" height="25"/>
-                            <constraints>
-                                <constraint firstAttribute="width" constant="25" id="aEr-j8-JDO"/>
-                                <constraint firstAttribute="height" constant="25" id="bvx-Uh-NWD"/>
-                            </constraints>
-                            <state key="normal" image="moreBig"/>
-                            <connections>
-                                <action selector="touchUpInsideMore:" destination="tys-A2-nDX" eventType="touchUpInside" id="Jyu-Mx-nWq"/>
-                            </connections>
-                        </button>
-                    </subviews>
-                    <constraints>
-                        <constraint firstItem="1LD-cd-zhc" firstAttribute="centerY" secondItem="s4I-Jo-yCE" secondAttribute="centerY" id="9mz-E0-K4B"/>
-                        <constraint firstItem="0bo-yl-t5k" firstAttribute="centerY" secondItem="1LD-cd-zhc" secondAttribute="centerY" id="URP-Ct-vPP"/>
-                        <constraint firstItem="D0O-wK-14O" firstAttribute="centerY" secondItem="1LD-cd-zhc" secondAttribute="centerY" id="UUF-sF-n6M"/>
-                        <constraint firstItem="0bo-yl-t5k" firstAttribute="leading" secondItem="1LD-cd-zhc" secondAttribute="trailing" constant="10" id="VBJ-H7-cJ3"/>
-                        <constraint firstAttribute="trailing" secondItem="D0O-wK-14O" secondAttribute="trailing" constant="10" id="WZh-iW-MXC"/>
-                        <constraint firstItem="1LD-cd-zhc" firstAttribute="leading" secondItem="s4I-Jo-yCE" secondAttribute="leading" constant="10" id="dGi-5z-MEh"/>
-                        <constraint firstAttribute="height" constant="50" id="vvG-dH-6c1"/>
-                    </constraints>
-                </view>
-                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LZu-Te-clJ">
-                    <rect key="frame" x="0.0" y="69" width="574" height="1"/>
-                    <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <constraints>
-                        <constraint firstAttribute="height" constant="1" id="VuP-sT-hUI"/>
-                    </constraints>
-                </view>
                 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NC1-5C-E5z" userLabel="View RichWorkspace">
                     <rect key="frame" x="0.0" y="318" width="574" height="50"/>
                     <subviews>
@@ -164,25 +110,16 @@
             <viewLayoutGuide key="safeArea" id="pm7-uW-mZE"/>
             <constraints>
                 <constraint firstItem="f9U-NY-4OS" firstAttribute="leading" secondItem="pm7-uW-mZE" secondAttribute="leading" id="7kv-IL-kwZ"/>
-                <constraint firstItem="s4I-Jo-yCE" firstAttribute="leading" secondItem="pm7-uW-mZE" secondAttribute="leading" id="CaM-Eb-nHq"/>
-                <constraint firstItem="LZu-Te-clJ" firstAttribute="leading" secondItem="pm7-uW-mZE" secondAttribute="leading" id="CyS-jg-0vc"/>
                 <constraint firstItem="pm7-uW-mZE" firstAttribute="trailing" secondItem="f9U-NY-4OS" secondAttribute="trailing" id="GbG-un-mCe"/>
                 <constraint firstItem="f9U-NY-4OS" firstAttribute="top" secondItem="I6b-6a-TKg" secondAttribute="bottom" id="JKM-HM-WpK"/>
-                <constraint firstItem="pm7-uW-mZE" firstAttribute="trailing" secondItem="LZu-Te-clJ" secondAttribute="trailing" id="NiW-2m-3HS"/>
                 <constraint firstItem="NC1-5C-E5z" firstAttribute="leading" secondItem="pm7-uW-mZE" secondAttribute="leading" id="QpF-nE-s7J"/>
                 <constraint firstItem="pm7-uW-mZE" firstAttribute="trailing" secondItem="NC1-5C-E5z" secondAttribute="trailing" id="UH6-8N-JUD"/>
-                <constraint firstItem="s4I-Jo-yCE" firstAttribute="top" secondItem="pm7-uW-mZE" secondAttribute="top" id="bSn-X7-YZH"/>
                 <constraint firstItem="pm7-uW-mZE" firstAttribute="trailing" secondItem="I6b-6a-TKg" secondAttribute="trailing" id="eYb-BW-clZ"/>
-                <constraint firstItem="LZu-Te-clJ" firstAttribute="top" secondItem="s4I-Jo-yCE" secondAttribute="bottom" constant="-1" id="ede-24-v8F"/>
                 <constraint firstItem="pm7-uW-mZE" firstAttribute="bottom" secondItem="f9U-NY-4OS" secondAttribute="bottom" id="eyu-CE-rTX"/>
-                <constraint firstItem="pm7-uW-mZE" firstAttribute="trailing" secondItem="s4I-Jo-yCE" secondAttribute="trailing" id="oCg-UW-8TQ"/>
                 <constraint firstItem="I6b-6a-TKg" firstAttribute="leading" secondItem="pm7-uW-mZE" secondAttribute="leading" id="pap-j1-yYG"/>
                 <constraint firstItem="NC1-5C-E5z" firstAttribute="bottom" secondItem="I6b-6a-TKg" secondAttribute="top" id="pmY-5s-Pv2"/>
             </constraints>
             <connections>
-                <outlet property="buttonMore" destination="D0O-wK-14O" id="eEx-3R-zCS"/>
-                <outlet property="buttonOrder" destination="0bo-yl-t5k" id="Kbw-BG-73C"/>
-                <outlet property="buttonSwitch" destination="1LD-cd-zhc" id="Ec2-cM-CoY"/>
                 <outlet property="buttonTransfer" destination="aS9-DV-CXI" id="Qsu-aQ-Vh7"/>
                 <outlet property="imageButtonTransfer" destination="Pgk-le-540" id="ljU-AW-YSt"/>
                 <outlet property="labelSection" destination="mB5-5n-AL9" id="uxf-bN-nZA"/>
@@ -191,14 +128,10 @@
                 <outlet property="textViewRichWorkspace" destination="pYo-pF-MGv" id="2h4-LP-T1z"/>
                 <outlet property="transferSeparatorBottom" destination="McE-3D-mc5" id="kJU-kh-04F"/>
                 <outlet property="transferSeparatorBottomHeightConstraint" destination="bJs-JY-WbC" id="P9i-Em-ycA"/>
-                <outlet property="viewButtonsView" destination="s4I-Jo-yCE" id="FOI-ZK-1oj"/>
-                <outlet property="viewButtonsViewHeightConstraint" destination="vvG-dH-6c1" id="SEQ-Tn-EE0"/>
                 <outlet property="viewRichWorkspace" destination="NC1-5C-E5z" id="NyN-tr-sJl"/>
                 <outlet property="viewRichWorkspaceHeightConstraint" destination="eT3-4m-mJ6" id="agb-tE-jhw"/>
                 <outlet property="viewSection" destination="f9U-NY-4OS" id="idM-C9-2nP"/>
                 <outlet property="viewSectionHeightConstraint" destination="ZcL-Wd-xhN" id="RDs-yy-I6W"/>
-                <outlet property="viewSeparator" destination="LZu-Te-clJ" id="rz1-2Q-vEK"/>
-                <outlet property="viewSeparatorHeightConstraint" destination="VuP-sT-hUI" id="QHV-oY-E5w"/>
                 <outlet property="viewTransfer" destination="I6b-6a-TKg" id="Hqx-QM-daQ"/>
                 <outlet property="viewTransferHeightConstraint" destination="86k-97-oGl" id="Pjb-mP-5dn"/>
             </connections>
@@ -206,14 +139,9 @@
         </collectionReusableView>
     </objects>
     <resources>
-        <image name="moreBig" width="50" height="50"/>
         <image name="stop.circle" catalog="system" width="128" height="123"/>
-        <image name="switchList" width="25" height="25"/>
-        <systemColor name="darkTextColor">
-            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-        </systemColor>
         <systemColor name="labelColor">
-            <color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
         </systemColor>
     </resources>
 </document>

+ 56 - 13
iOSClient/Media/NCMedia.swift

@@ -33,6 +33,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
     var mediaCommandView: NCMediaCommandView?
     var layout: NCMediaGridLayout!
     var documentPickerViewController: NCDocumentPickerViewController?
+    var tabBarSelect: NCMediaSelectTabBar?
 
     let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
     let utilityFileSystem = NCUtilityFileSystem()
@@ -53,7 +54,7 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
     var lastContentOffsetY: CGFloat = 0
     var mediaPath = ""
 
-    var timeIntervalSearchNewMedia: TimeInterval = 3.0
+    var timeIntervalSearchNewMedia: TimeInterval = 2.0
     var timerSearchNewMedia: Timer?
 
     let insetsTop: CGFloat = 75
@@ -87,12 +88,15 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
         mediaCommandView = Bundle.main.loadNibNamed("NCMediaCommandView", owner: self, options: nil)?.first as? NCMediaCommandView
         self.view.addSubview(mediaCommandView!)
         mediaCommandView?.mediaView = self
+        mediaCommandView?.tabBarController = tabBarController
         mediaCommandView?.translatesAutoresizingMaskIntoConstraints = false
         mediaCommandView?.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
         mediaCommandView?.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
         mediaCommandView?.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
         mediaCommandView?.heightAnchor.constraint(equalToConstant: 150).isActive = true
 
+        tabBarSelect = NCMediaSelectTabBar(tabBarController: self.tabBarController, delegate: mediaCommandView)
+
         cacheImages.cellLivePhotoImage = utility.loadImage(named: "livephoto", color: .white)
         cacheImages.cellPlayImage = utility.loadImage(named: "play.fill", color: .white)
 
@@ -113,15 +117,15 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
 
         if let metadatas = NCImageCache.shared.initialMetadatas() {
             self.metadatas = metadatas
-            self.mediaCommandView?.setMoreButton()
         }
+
         collectionView.reloadData()
     }
 
     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
 
-        mediaCommandView?.setMediaCommand()
+        mediaCommandView?.setTitleDate()
         mediaCommandView?.createMenu()
     }
 
@@ -135,11 +139,24 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
         super.viewWillTransition(to: size, with: coordinator)
 
         collectionView?.collectionViewLayout.invalidateLayout()
-        mediaCommandView?.setMediaCommand()
+        mediaCommandView?.setTitleDate()
     }
 
     override var preferredStatusBarStyle: UIStatusBarStyle {
-        return .lightContent
+        if self.traitCollection.userInterfaceStyle == .dark {
+            return .lightContent
+        } else if let gradient = mediaCommandView?.gradient, gradient.isHidden {
+            return .darkContent
+        } else {
+            return .lightContent
+        }
+    }
+
+    override func viewWillLayoutSubviews() {
+        super.viewWillLayoutSubviews()
+        if let frame = tabBarController?.tabBar.frame {
+            tabBarSelect?.hostingController.view.frame = frame
+        }
     }
 
     // MARK: - NotificationCenter
@@ -219,9 +236,8 @@ extension NCMedia: UICollectionViewDelegate {
                 } else {
                     selectOcId.append(metadata.ocId)
                 }
-                if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) {
-                    collectionView.reloadItems(at: [indexPath])
-                }
+                collectionView.reloadItems(at: [indexPath])
+                tabBarSelect?.selectCount = selectOcId.count
             } else {
                 // ACTIVE SERVERURL
                 appDelegate.activeServerUrl = metadata.serverUrl
@@ -247,6 +263,7 @@ extension NCMedia: UICollectionViewDelegate {
     }
 
     func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
+
         animator.addCompletion {
             if let indexPath = configuration.identifier as? IndexPath {
                 self.collectionView(collectionView, didSelectItemAt: indexPath)
@@ -256,6 +273,7 @@ extension NCMedia: UICollectionViewDelegate {
 }
 
 extension NCMedia: UICollectionViewDataSourcePrefetching {
+
     func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
         // print("[LOG] n. " + String(indexPaths.count))
     }
@@ -264,16 +282,36 @@ extension NCMedia: UICollectionViewDataSourcePrefetching {
 extension NCMedia: UICollectionViewDataSource {
 
     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+
         var numberOfItemsInSection = 0
+
         if let metadatas {
             numberOfItemsInSection = metadatas.count
         }
+
+        if numberOfItemsInSection == 0 {
+            mediaCommandView?.selectOrCancelButton.isHidden = true
+            mediaCommandView?.menuButton.isHidden = false
+            mediaCommandView?.activityIndicatorTrailing.constant = 46
+        } else if isEditMode {
+            mediaCommandView?.selectOrCancelButton.isHidden = false
+            mediaCommandView?.menuButton.isHidden = true
+            mediaCommandView?.activityIndicatorTrailing.constant = 144
+        } else {
+            mediaCommandView?.selectOrCancelButton.isHidden = false
+            mediaCommandView?.menuButton.isHidden = false
+            mediaCommandView?.activityIndicatorTrailing.constant = 144
+        }
+
         emptyDataSet?.numberOfItemsInSection(numberOfItemsInSection, section: section)
+
         return numberOfItemsInSection
     }
 
     func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+
         guard let metadatas else { return }
+
         if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < metadatas.count {
             guard let metadata = metadatas[indexPath.row] else { return }
             for case let operation as NCMediaDownloadThumbnaill in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
@@ -337,14 +375,20 @@ extension NCMedia: UICollectionViewDataSource {
 extension NCMedia: UIScrollViewDelegate {
 
     func scrollViewDidScroll(_ scrollView: UIScrollView) {
-        if lastContentOffsetY == 0 || lastContentOffsetY + cellHeigth / 2 <= scrollView.contentOffset.y || lastContentOffsetY - cellHeigth / 2 >= scrollView.contentOffset.y {
-            mediaCommandView?.setMediaCommand()
-            lastContentOffsetY = scrollView.contentOffset.y
+        if let metadatas, !metadatas.isEmpty {
+            let isTop = scrollView.contentOffset.y <= -(insetsTop + view.safeAreaInsets.top - 35)
+            mediaCommandView?.setColor(isTop: isTop)
+            setNeedsStatusBarAppearanceUpdate()
+            if lastContentOffsetY == 0 || lastContentOffsetY + cellHeigth / 2 <= scrollView.contentOffset.y || lastContentOffsetY - cellHeigth / 2 >= scrollView.contentOffset.y {
+                mediaCommandView?.setTitleDate()
+                lastContentOffsetY = scrollView.contentOffset.y
+            }
+        } else {
+            mediaCommandView?.setColor(isTop: true)
         }
     }
 
     func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
-
     }
 
     func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
@@ -372,7 +416,6 @@ extension NCMedia: UIScrollViewDelegate {
 extension NCMedia: NCSelectDelegate {
 
     func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) {
-
         guard let serverUrl = serverUrl else { return }
         let home = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
         mediaPath = serverUrl.replacingOccurrences(of: home, with: "")

+ 94 - 93
iOSClient/Media/NCMediaCommandView.swift

@@ -27,11 +27,14 @@ import NextcloudKit
 class NCMediaCommandView: UIView {
 
     @IBOutlet weak var title: UILabel!
-    @IBOutlet weak var selectButton: UIButton!
     @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
-    @IBOutlet weak var moreButton: UIButton!
+    @IBOutlet weak var activityIndicatorTrailing: NSLayoutConstraint!
+    @IBOutlet weak var selectOrCancelButton: UIButton!
+    @IBOutlet weak var selectOrCancelButtonTrailing: NSLayoutConstraint!
+    @IBOutlet weak var menuButton: UIButton!
 
     var mediaView: NCMedia!
+    var tabBarController: UITabBarController?
     var attributesZoomIn: UIMenuElement.Attributes = []
     var attributesZoomOut: UIMenuElement.Attributes = []
     let gradient: CAGradientLayer = CAGradientLayer()
@@ -41,12 +44,20 @@ class NCMediaCommandView: UIView {
 
         title.text = ""
 
-        selectButton.backgroundColor = .systemGray4.withAlphaComponent(0.6)
-        selectButton.layer.cornerRadius = 15
-        selectButton.layer.masksToBounds = true
-        selectButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
-
-        moreButton.changesSelectionAsPrimaryAction = false
+        selectOrCancelButton.backgroundColor = nil
+        selectOrCancelButton.layer.cornerRadius = 15
+        selectOrCancelButton.layer.masksToBounds = true
+        selectOrCancelButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
+        selectOrCancelButton.addBlur(style: .systemThinMaterial)
+
+        menuButton.backgroundColor = nil
+        menuButton.layer.cornerRadius = 15
+        menuButton.layer.masksToBounds = true
+        menuButton.showsMenuAsPrimaryAction = true
+        menuButton.configuration = UIButton.Configuration.plain()
+        menuButton.setImage(UIImage(systemName: "ellipsis"), for: .normal)
+        menuButton.changesSelectionAsPrimaryAction = false
+        menuButton.addBlur(style: .systemThinMaterial)
 
         gradient.frame = bounds
         gradient.startPoint = CGPoint(x: 0, y: 0.5)
@@ -60,78 +71,60 @@ class NCMediaCommandView: UIView {
         gradient.frame = bounds
     }
 
-    @IBAction func selectButtonPressed(_ sender: UIButton) {
+    @IBAction func selectOrCancelButtonPressed(_ sender: UIButton) {
+
+        mediaView.isEditMode = !mediaView.isEditMode
+        setSelectcancelButton()
+    }
+
+    func setSelectcancelButton() {
+
+        mediaView.selectOcId.removeAll()
+        mediaView.tabBarSelect?.selectCount = mediaView.selectOcId.count
+
         if mediaView.isEditMode {
-            setMoreButton()
+            selectOrCancelButton.setTitle( NSLocalizedString("_cancel_", comment: ""), for: .normal)
+            selectOrCancelButtonTrailing.constant = 8
+            mediaView.tabBarSelect?.show()
         } else {
-            setMoreButtonDelete()
+            selectOrCancelButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
+            selectOrCancelButtonTrailing.constant = 46
+            mediaView.tabBarSelect?.hide()
         }
-    }
-
-    @IBAction func trashButtonPressed(_ sender: UIButton) {
 
-        if !mediaView.selectOcId.isEmpty {
-            let selectOcId = mediaView.selectOcId
-            var title = NSLocalizedString("_delete_", comment: "")
-            if selectOcId.count > 1 {
-                title = NSLocalizedString("_delete_selected_files_", comment: "")
-            }
-            let alertController = UIAlertController(
-                title: title,
-                message: "",
-                preferredStyle: .alert)
-            alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_delete_", comment: ""), style: .default) { (_: UIAlertAction) in
+        mediaView.collectionView.reloadData()
+    }
 
-                self.setMoreButton()
+    func setTitleDate() {
 
-                Task {
-                    var error = NKError()
-                    var ocIds: [String] = []
-                    for ocId in selectOcId where error == .success {
-                        if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
-                            error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false)
-                            if error == .success {
-                                ocIds.append(metadata.ocId)
-                            }
-                        }
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            self.title.text = ""
+            if let visibleCells = self.mediaView.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.mediaView.collectionView?.cellForItem(at: $0) }) {
+                if let cell = visibleCells.first as? NCGridMediaCell {
+                    self.title.text = ""
+                    if let date = cell.date {
+                        self.title.text = self.mediaView.utility.getTitleFromDate(date)
                     }
-                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocIds, "onlyLocalCache": false, "error": error])
                 }
-            })
-            alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_delete_", comment: ""), style: .default) { (_: UIAlertAction) in })
-
-            mediaView.present(alertController, animated: true, completion: { })
+            }
         }
     }
 
-    func setMoreButton() {
-
-        moreButton.backgroundColor = .systemGray4.withAlphaComponent(0.6)
-        moreButton.layer.cornerRadius = 15
-        moreButton.layer.masksToBounds = true
-        moreButton.showsMenuAsPrimaryAction = true
-        moreButton.configuration = UIButton.Configuration.plain()
-        let image = UIImage(systemName: "ellipsis")
-        moreButton.setImage(image, for: .normal)
-
-        mediaView.isEditMode = false
-        mediaView.selectOcId.removeAll()
-        mediaView.collectionView?.reloadData()
-
-        selectButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
-    }
-
-    func setMoreButtonDelete() {
-
-        moreButton.backgroundColor = .clear
-        moreButton.showsMenuAsPrimaryAction = false
-        moreButton.configuration = UIButton.Configuration.plain()
-        let image = UIImage(systemName: "trash.circle", withConfiguration: UIImage.SymbolConfiguration(pointSize: 25))?.withTintColor(.red, renderingMode: .alwaysOriginal)
-        moreButton.setImage(image, for: .normal)
-
-        mediaView.isEditMode = true
+    func setColor(isTop: Bool) {
 
-        selectButton.setTitle( NSLocalizedString("_cancel_", comment: ""), for: .normal)
+        if isTop {
+            title.textColor = .label
+            activityIndicator.color = .label
+            selectOrCancelButton.setTitleColor(.label, for: .normal)
+            menuButton.setImage(UIImage(systemName: "ellipsis")?.withTintColor(.label, renderingMode: .alwaysOriginal), for: .normal)
+            gradient.isHidden = true
+        } else {
+            title.textColor = .white
+            activityIndicator.color = .white
+            selectOrCancelButton.setTitleColor(.white, for: .normal)
+            menuButton.setImage(UIImage(systemName: "ellipsis")?.withTintColor(.white, renderingMode: .alwaysOriginal), for: .normal)
+            gradient.isHidden = false
+        }
     }
 
     func createMenu() {
@@ -217,36 +210,44 @@ class NCMediaCommandView: UIView {
             self.mediaView.present(alert, animated: true)
         }
 
-        moreButton.menu = UIMenu(title: "", children: [topAction, playFile, playURL])
+        menuButton.menu = UIMenu(title: "", children: [topAction, playFile, playURL])
     }
+}
 
-    func setMediaCommand() {
+// MARK: - NCMediaTabBarSelectDelegate
 
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-            self.title.text = ""
-            if let visibleCells = self.mediaView.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.mediaView.collectionView?.cellForItem(at: $0) }) {
-                if let cell = visibleCells.first as? NCGridMediaCell {
-                    self.title.text = ""
-                    if let date = cell.date {
-                        self.title.text = self.mediaView.utility.getTitleFromDate(date)
-                    }
-                }
-            }
-            if let metadatas = self.mediaView.metadatas, !metadatas.isEmpty {
-                self.selectButton.isHidden = false
-                if self.gradient.isHidden {
-                    UIView.animate(withDuration: 0.3) {
-                        self.gradient.isHidden = false
-                    }
-                }
-            } else {
-                self.selectButton.isHidden = true
-                if !self.gradient.isHidden {
-                    UIView.animate(withDuration: 0.3) {
-                        self.gradient.isHidden = true
+extension NCMediaCommandView: NCMediaSelectTabBarDelegate {
+
+    func delete() {
+
+        if !mediaView.selectOcId.isEmpty {
+            let selectOcId = mediaView.selectOcId
+            let alertController = UIAlertController(
+                title: NSLocalizedString("_delete_selected_photos_", comment: ""),
+                message: "",
+                preferredStyle: .alert)
+            alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default) { (_: UIAlertAction) in
+
+                Task {
+                    var error = NKError()
+                    var ocIds: [String] = []
+                    for ocId in selectOcId where error == .success {
+                        if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
+                            error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false)
+                            if error == .success {
+                                ocIds.append(metadata.ocId)
+                            }
+                        }
                     }
+                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocIds, "onlyLocalCache": false, "error": error])
                 }
-            }
+
+                self.mediaView.isEditMode = false
+                self.setSelectcancelButton()
+            })
+            alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .default) { (_: UIAlertAction) in })
+
+            mediaView.present(alertController, animated: true, completion: { })
         }
     }
 }

+ 21 - 22
iOSClient/Media/NCMediaCommandView.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina6_12" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22130"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -20,25 +20,13 @@
                         <constraint firstAttribute="height" constant="40" id="S6o-Pa-sxy"/>
                     </constraints>
                     <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
-                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="textColor"/>
                     <nil key="highlightedColor"/>
                 </label>
                 <activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="XVj-jD-9KA">
                     <rect key="frame" x="229" y="77" width="20" height="20"/>
                     <color key="color" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                 </activityIndicatorView>
-                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3qs-Hm-qLL">
-                    <rect key="frame" x="355" y="72" width="30" height="30"/>
-                    <constraints>
-                        <constraint firstAttribute="height" constant="30" id="l6a-hf-7l2"/>
-                        <constraint firstAttribute="width" constant="30" id="uVw-bC-TZq"/>
-                    </constraints>
-                    <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <inset key="imageEdgeInsets" minX="4" minY="4" maxX="4" maxY="4"/>
-                    <connections>
-                        <action selector="trashButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="7mo-8p-bep"/>
-                    </connections>
-                </button>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EFV-eb-pFF">
                     <rect key="frame" x="257" y="72" width="90" height="30"/>
                     <constraints>
@@ -47,11 +35,20 @@
                     </constraints>
                     <fontDescription key="fontDescription" type="system" pointSize="14"/>
                     <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
-                    <state key="normal" title="Select"/>
+                    <state key="normal" title="Title"/>
                     <connections>
-                        <action selector="selectButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="YfJ-ms-0G1"/>
+                        <action selector="selectOrCancelButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="0dX-pC-icF"/>
                     </connections>
                 </button>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3qs-Hm-qLL">
+                    <rect key="frame" x="355" y="72" width="30" height="30"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="30" id="l6a-hf-7l2"/>
+                        <constraint firstAttribute="width" constant="30" id="uVw-bC-TZq"/>
+                    </constraints>
+                    <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <inset key="imageEdgeInsets" minX="4" minY="4" maxX="4" maxY="4"/>
+                </button>
             </subviews>
             <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
             <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -59,20 +56,22 @@
                 <constraint firstItem="XVj-jD-9KA" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="4nZ-Ea-KMB"/>
                 <constraint firstItem="3qs-Hm-qLL" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="AgJ-WD-mqU"/>
                 <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="3qs-Hm-qLL" secondAttribute="trailing" constant="8" id="FtF-ES-dyl"/>
-                <constraint firstItem="EFV-eb-pFF" firstAttribute="leading" secondItem="XVj-jD-9KA" secondAttribute="trailing" constant="8" id="I7N-mB-wyc"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="EFV-eb-pFF" secondAttribute="trailing" constant="46" id="OIB-Zp-XkO"/>
                 <constraint firstItem="IxY-xH-yZQ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="ZhO-pY-Qwi"/>
                 <constraint firstItem="IxY-xH-yZQ" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="mX3-Fm-K1m"/>
                 <constraint firstItem="EFV-eb-pFF" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="ozT-m6-dct"/>
-                <constraint firstItem="3qs-Hm-qLL" firstAttribute="leading" secondItem="EFV-eb-pFF" secondAttribute="trailing" constant="8" id="t2r-N2-lJR"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="XVj-jD-9KA" secondAttribute="trailing" constant="144" id="uEq-dt-udC"/>
                 <constraint firstItem="XVj-jD-9KA" firstAttribute="leading" secondItem="IxY-xH-yZQ" secondAttribute="trailing" constant="8" id="ztz-0d-9Mr"/>
             </constraints>
             <connections>
                 <outlet property="activityIndicator" destination="XVj-jD-9KA" id="cSB-RJ-RCZ"/>
-                <outlet property="moreButton" destination="3qs-Hm-qLL" id="OPi-J6-wkl"/>
-                <outlet property="selectButton" destination="EFV-eb-pFF" id="AJu-vY-GS3"/>
+                <outlet property="activityIndicatorTrailing" destination="uEq-dt-udC" id="pvB-8X-Jpb"/>
+                <outlet property="menuButton" destination="3qs-Hm-qLL" id="7uo-w1-pml"/>
+                <outlet property="selectOrCancelButton" destination="EFV-eb-pFF" id="2ve-si-IjY"/>
+                <outlet property="selectOrCancelButtonTrailing" destination="OIB-Zp-XkO" id="2gp-In-Snh"/>
                 <outlet property="title" destination="IxY-xH-yZQ" id="ZNZ-Jy-JbH"/>
             </connections>
-            <point key="canvasLocation" x="138.75" y="152.5"/>
+            <point key="canvasLocation" x="140" y="148"/>
         </view>
     </objects>
 </document>

+ 7 - 7
iOSClient/Media/NCMediaDataSource.swift

@@ -47,7 +47,7 @@ extension NCMedia {
         self.metadatas = NCImageCache.shared.getMediaMetadatas(account: self.appDelegate.account, predicate: self.getPredicate())
         DispatchQueue.main.async {
             self.collectionView?.reloadData()
-            self.mediaCommandView?.setMediaCommand()
+            self.mediaCommandView?.setTitleDate()
         }
     }
 
@@ -94,28 +94,28 @@ extension NCMedia {
                     await self.collectionView.reloadData()
                     let results = await self.searchMedia(account: self.appDelegate.account, lessDate: lessDate, greaterDate: greaterDate)
                     print("Media results changed items: \(results.isChanged)")
-                    if results.error != .success {
-                        NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(results.error.errorCode) " + results.error.errorDescription)
-                    }
                     await self.mediaCommandView?.activityIndicator.stopAnimating()
                     Task { @MainActor in
                         self.loadingTask = nil
                     }
-                    if results.error == .success, results.lessDate == Date.distantFuture, results.greaterDate == Date.distantPast, !results.isChanged, results.metadatasCount == 0 {
+                    if results.error != .success {
+                        NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(results.error.errorCode) " + results.error.errorDescription)
+                    } else if results.error == .success, results.lessDate == Date.distantFuture, results.greaterDate == Date.distantPast, !results.isChanged, results.metadatasCount == 0 {
                         Task { @MainActor in
                             self.metadatas = nil
                         }
-                        await self.collectionView.reloadData()
                     }
                     if results.isChanged {
                         await self.reloadDataSource()
+                    } else {
+                        await self.collectionView.reloadData()
                     }
                 }
             }
         }
     }
 
-    func searchMedia(account: String, lessDate: Date, greaterDate: Date, limit: Int = 300, timeout: TimeInterval = 60) async -> (account: String, lessDate: Date?, greaterDate: Date?, metadatasCount: Int, isChanged: Bool, error: NKError) {
+    func searchMedia(account: String, lessDate: Date, greaterDate: Date, limit: Int = 120, timeout: TimeInterval = 60) async -> (account: String, lessDate: Date?, greaterDate: Date?, metadatasCount: Int, isChanged: Bool, error: NKError) {
 
         guard let mediaPath = NCManageDatabase.shared.getActiveAccount()?.mediaPath else {
             return(account, lessDate, greaterDate, 0, false, NKError())

+ 15 - 0
iOSClient/Media/NCMediaGridLayout.swift

@@ -5,6 +5,21 @@
 //  Created by Marino Faggiana on 27/01/24.
 //  Copyright © 2024 Marino Faggiana. All rights reserved.
 //
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
 
 import UIKit
 

+ 108 - 0
iOSClient/Media/NCMediaSelectTabBar.swift

@@ -0,0 +1,108 @@
+//
+//  NCMediaTabbarSelect.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 01/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import SwiftUI
+
+protocol NCMediaSelectTabBarDelegate: AnyObject {
+    func delete()
+}
+
+class NCMediaSelectTabBar: ObservableObject {
+    var hostingController: UIViewController!
+    var mediaTabBarController: UITabBarController?
+    open weak var delegate: NCMediaSelectTabBarDelegate?
+    @Published var selectCount: Int = 0
+
+    init(tabBarController: UITabBarController? = nil, delegate: NCMediaSelectTabBarDelegate? = nil) {
+        guard let tabBarController else { return }
+        let mediaTabBarSelectView = MediaTabBarSelectView(tabBarSelect: self)
+        hostingController = UIHostingController(rootView: mediaTabBarSelectView)
+
+        self.mediaTabBarController = tabBarController
+        self.delegate = delegate
+
+        tabBarController.view.addSubview(hostingController.view)
+
+        hostingController.view.frame = tabBarController.tabBar.frame
+        hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        hostingController.view.backgroundColor = .clear
+        hostingController.view.isHidden = true
+    }
+
+    func show() {
+        hostingController.view.isHidden = false
+        hostingController.view.transform = .init(translationX: 0, y: hostingController.view.frame.height)
+        UIView.animate(withDuration: 0.2) {
+            self.hostingController.view.transform = .init(translationX: 0, y: 0)
+        }
+        mediaTabBarController?.tabBar.isHidden = true
+    }
+
+    func hide() {
+        mediaTabBarController?.tabBar.isHidden = false
+        hostingController.view.isHidden = true
+    }
+}
+
+struct MediaTabBarSelectView: View {
+    @ObservedObject var tabBarSelect: NCMediaSelectTabBar
+    @Environment(\.verticalSizeClass) var sizeClass
+
+    var body: some View {
+        VStack {
+            Spacer().frame(height: sizeClass == .compact ? 5 : 10)
+            HStack {
+                Spacer().frame(maxWidth: .infinity)
+                Group {
+                    if tabBarSelect.selectCount == 0 {
+                        Text(NSLocalizedString("_select_photos_", comment: ""))
+                    } else if tabBarSelect.selectCount == 1 {
+                        Text(String(tabBarSelect.selectCount) + " " + NSLocalizedString("_selected_photo_", comment: ""))
+                    } else {
+                        Text(String(tabBarSelect.selectCount) + " " + NSLocalizedString("_selected_photos_", comment: ""))
+                    }
+                }
+                .frame(minWidth: 250, maxWidth: .infinity)
+
+                Button {
+                    tabBarSelect.delegate?.delete()
+                } label: {
+                    Image(systemName: "trash")
+                    .imageScale(sizeClass == .compact ? .medium : .large)
+                }
+                .tint(.red)
+                .disabled(tabBarSelect.selectCount == 0)
+                .frame(maxWidth: .infinity)
+            }
+            .frame(maxWidth: .infinity)
+        }
+        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+        .background(.thinMaterial)
+        .overlay(Rectangle().frame(width: nil, height: 0.5, alignment: .top).foregroundColor(Color(UIColor.separator)), alignment: .top)
+    }
+}
+
+#Preview {
+    MediaTabBarSelectView(tabBarSelect: NCMediaSelectTabBar())
+}

+ 30 - 17
iOSClient/Menu/AppDelegate+Menu.swift

@@ -59,22 +59,15 @@ extension AppDelegate {
         )
 
         if NextcloudKit.shared.isNetworkReachable() && directEditingCreators != nil && directEditingCreators!.contains(where: { $0.editor == NCGlobal.shared.editorText}) && !isDirectoryE2EE {
-            let directEditingCreator = directEditingCreators!.first(where: { $0.editor == NCGlobal.shared.editorText})!
             actions.append(
                 NCMenuAction(title: NSLocalizedString("_create_nextcloudtext_document_", comment: ""), icon: UIImage(named: "file_txt")!.image(color: UIColor.systemGray, size: 50), action: { _ in
-                    guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController() else {
-                        return
-                    }
-                    navigationController.modalPresentationStyle = UIModalPresentationStyle.formSheet
+                    let directEditingCreator = directEditingCreators!.first(where: { $0.editor == NCGlobal.shared.editorText})!
 
-                    if let viewController = (navigationController as? UINavigationController)?.topViewController as? NCCreateFormUploadDocuments {
-                        viewController.editorId = NCGlobal.shared.editorText
-                        viewController.creatorId = directEditingCreator.identifier
-                        viewController.typeTemplate = NCGlobal.shared.templateDocument
-                        viewController.serverUrl = appDelegate.activeServerUrl
-                        viewController.titleForm = NSLocalizedString("_create_nextcloudtext_document_", comment: "")
+                    Task {
+                        let fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + ".md", account: appDelegate.account, serverUrl: self.activeServerUrl)
 
-                        appDelegate.window?.rootViewController?.present(navigationController, animated: true, completion: nil)
+                        let fileNamePath = NCUtilityFileSystem().getFileNamePath(String(describing: fileName), serverUrl: appDelegate.activeServerUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId)
+                        self.createTextDocument(fileNamePath: fileNamePath, fileName: String(describing: fileName), creatorId: directEditingCreator.identifier)
                     }
                 })
             )
@@ -95,14 +88,9 @@ extension AppDelegate {
                 title: NSLocalizedString("_create_voice_memo_", comment: ""), icon: UIImage(named: "microphone")!.image(color: UIColor.systemGray, size: 50), action: { _ in
                     NCAskAuthorization().askAuthorizationAudioRecord(viewController: viewController) { hasPermission in
                         if hasPermission {
-                            let fileName = NCUtilityFileSystem().createFileNameDate(NSLocalizedString("_voice_memo_filename_", comment: ""), ext: "m4a")
                             if let viewController = UIStoryboard(name: "NCAudioRecorderViewController", bundle: nil).instantiateInitialViewController() as? NCAudioRecorderViewController {
-
-                                viewController.delegate = self
-                                viewController.createRecorder(fileName: fileName)
                                 viewController.modalTransitionStyle = .crossDissolve
                                 viewController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
-
                                 appDelegate.window?.rootViewController?.present(viewController, animated: true, completion: nil)
                             }
                         }
@@ -299,4 +287,29 @@ extension AppDelegate {
 
         viewController.presentMenu(with: actions)
     }
+
+    func createTextDocument(fileNamePath: String, fileName: String, creatorId: String) {
+        var UUID = NSUUID().uuidString
+        UUID = "TEMP" + UUID.replacingOccurrences(of: "-", with: "")
+
+        let options = NKRequestOptions(customUserAgent: NCUtility().getCustomUserAgentNCText())
+
+        NextcloudKit.shared.NCTextCreateFile(fileNamePath: fileNamePath, editorId: NCGlobal.shared.editorText, creatorId: creatorId, templateId: NCGlobal.shared.templateDocument, options: options) { account, url, _, error in
+            guard error == .success, account == self.account, let url = url else {
+                NCContentPresenter().showError(error: error)
+                return
+            }
+
+            var results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false)
+            // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
+            if results.mimeType.isEmpty {
+                results.mimeType = "text/x-markdown"
+            }
+
+            let metadata = NCManageDatabase.shared.createMetadata(account: self.account, user: self.user, userId: self.userId, fileName: fileName, fileNameView: fileName, ocId: UUID, serverUrl: self.activeServerUrl, urlBase: self.urlBase, url: url, contentType: results.mimeType)
+            if let viewController = self.activeViewController {
+                NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
+            }
+        }
+    }
 }

+ 31 - 31
iOSClient/Menu/NCCollectionViewCommon+Menu.swift

@@ -65,12 +65,15 @@ extension NCCollectionViewCommon {
         actions.append(
             NCMenuAction(
                 title: metadata.fileNameView,
+                boldTitle: true,
                 icon: iconHeader,
                 order: 0,
                 action: nil
             )
         )
 
+        actions.append(.seperator(order: 1))
+
         //
         // DETAILS
         //
@@ -78,7 +81,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_details_", comment: ""),
-                    icon: utility.loadImage(named: "info"),
+                    icon: utility.loadImage(named: "info.circle"),
                     order: 10,
                     action: { _ in
                         NCActionCenter.shared.openShare(viewController: self, metadata: metadata, page: .activity)
@@ -146,7 +149,7 @@ extension NCCollectionViewCommon {
         //
         // SET FOLDER E2EE
         //
-        if metadata.isDirectoySettableE2EE {
+        if metadata.canSetDirectoryAsE2EE {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_e2e_set_folder_encrypted_", comment: ""),
@@ -167,7 +170,7 @@ extension NCCollectionViewCommon {
         //
         // UNSET FOLDER E2EE
         //
-        if metadata.isDirectoryUnsettableE2EE {
+        if metadata.canUnsetDirectoryAsE2EE {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_e2e_remove_folder_encrypted_", comment: ""),
@@ -190,8 +193,6 @@ extension NCCollectionViewCommon {
             )
         }
 
-        actions.append(.seperator(order: 40))
-
         //
         // FAVORITE
         // FIXME: PROPPATCH doesn't work
@@ -200,7 +201,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: metadata.favorite ? NSLocalizedString("_remove_favorites_", comment: "") : NSLocalizedString("_add_favorites_", comment: ""),
-                    icon: utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite),
+                    icon: utility.loadImage(named: metadata.favorite ? "star.slash.fill" : "star.fill", color: NCBrandColor.shared.yellowFavorite),
                     order: 50,
                     action: { _ in
                         NCNetworking.shared.favoriteMetadata(metadata) { error in
@@ -216,7 +217,7 @@ extension NCCollectionViewCommon {
         //
         // OFFLINE
         //
-        if metadata.isSettableOnOffline {
+        if metadata.canSetAsAvailableOffline {
             actions.append(.setAvailableOfflineAction(selectedMetadatas: [metadata], isAnyOffline: isOffline, viewController: self, order: 60, completion: {
                 self.reloadDataSource()
             }))
@@ -256,24 +257,26 @@ extension NCCollectionViewCommon {
         }
 
         //
-        // OPEN IN
+        // SHARE
         //
         if metadata.canOpenIn {
-            actions.append(.openInAction(selectedMetadatas: [metadata], viewController: self, order: 80))
-        }
-
-        //
-        // PRINT
-        //
-        if metadata.isPrintable {
-            actions.append(.printAction(metadata: metadata, order: 90))
+            actions.append(.share(selectedMetadatas: [metadata], viewController: self, order: 80))
         }
 
         //
-        // SAVE CAMERA ROLL
+        // SAVE LIVE PHOTO
         //
-        if metadata.isSavebleInCameraRoll {
-            actions.append(.saveMediaAction(selectedMediaMetadatas: [metadata], order: 100))
+        if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+            actions.append(
+                NCMenuAction(
+                    title: NSLocalizedString("_livephoto_save_", comment: ""),
+                    icon: NCUtility().loadImage(named: "livephoto"),
+                    order: 100,
+                    action: { _ in
+                        NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
+                    }
+                )
+            )
         }
 
         //
@@ -283,7 +286,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_save_as_scan_", comment: ""),
-                    icon: utility.loadImage(named: "viewfinder.circle"),
+                    icon: utility.loadImage(named: "doc.viewfinder"),
                     order: 110,
                     action: { _ in
                         if self.utilityFileSystem.fileProviderStorageExists(metadata) {
@@ -295,7 +298,9 @@ extension NCCollectionViewCommon {
                                            "error": NKError(),
                                            "account": metadata.account])
                         } else {
-                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorSaveAsScan) else { return }
+                            guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                                           session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                                           selector: NCGlobal.shared.selectorSaveAsScan) else { return }
                             NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
@@ -310,7 +315,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_rename_", comment: ""),
-                    icon: utility.loadImage(named: "pencil"),
+                    icon: utility.loadImage(named: "text.cursor"),
                     order: 120,
                     action: { _ in
 
@@ -336,13 +341,6 @@ extension NCCollectionViewCommon {
             actions.append(.moveOrCopyAction(selectedMetadatas: [metadata], indexPath: [indexPath], order: 130))
         }
 
-        //
-        // COPY IN PASTEBOARD
-        //
-        if metadata.isCopyableInPasteboard {
-            actions.append(.copyAction(selectOcId: [metadata.ocId], order: 140))
-        }
-
         //
         // MODIFY WITH QUICK LOOK
         //
@@ -362,7 +360,9 @@ extension NCCollectionViewCommon {
                                            "error": NKError(),
                                            "account": metadata.account])
                         } else {
-                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
+                            guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                                           session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                                           selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
                             NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
@@ -377,7 +377,7 @@ extension NCCollectionViewCommon {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_change_color_", comment: ""),
-                    icon: utility.loadImage(named: "palette"),
+                    icon: utility.loadImage(named: "paintpalette"),
                     order: 160,
                     action: { _ in
                         if let picker = UIStoryboard(name: "NCColorPicker", bundle: nil).instantiateInitialViewController() as? NCColorPicker {

+ 37 - 48
iOSClient/Menu/NCContextMenu.swift

@@ -36,11 +36,9 @@ class NCContextMenu: NSObject {
 
         var downloadRequest: DownloadRequest?
         var titleDeleteConfirmFile = NSLocalizedString("_delete_file_", comment: "")
-        var titleSave: String = NSLocalizedString("_save_selected_files_", comment: "")
         let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata)
 
         if metadata.directory { titleDeleteConfirmFile = NSLocalizedString("_delete_folder_", comment: "") }
-        if metadataMOV != nil { titleSave = NSLocalizedString("_livephoto_save_", comment: "") }
 
         let hud = JGProgressHUD()
         hud.indicatorView = JGProgressHUDRingIndicatorView()
@@ -56,14 +54,14 @@ class NCContextMenu: NSObject {
         // MENU ITEMS
 
         let detail = UIAction(title: NSLocalizedString("_details_", comment: ""),
-                              image: UIImage(systemName: "info")) { _ in
+                              image: UIImage(systemName: "info.circle")) { _ in
             NCActionCenter.shared.openShare(viewController: viewController, metadata: metadata, page: .activity)
         }
 
         let favorite = UIAction(title: metadata.favorite ?
                                 NSLocalizedString("_remove_favorites_", comment: "") :
                                 NSLocalizedString("_add_favorites_", comment: ""),
-                                image: utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite)) { _ in
+                                image: utility.loadImage(named: metadata.favorite ? "star.slash" : "star", color: NCBrandColor.shared.yellowFavorite)) { _ in
             NCNetworking.shared.favoriteMetadata(metadata) { error in
                 if error != .success {
                     NCContentPresenter().showError(error: error)
@@ -71,7 +69,7 @@ class NCContextMenu: NSObject {
             }
         }
 
-        let openIn = UIAction(title: NSLocalizedString("_open_in_", comment: ""),
+        let share = UIAction(title: NSLocalizedString("_share_", comment: ""),
                               image: UIImage(systemName: "square.and.arrow.up") ) { _ in
             if self.utilityFileSystem.fileProviderStorageExists(metadata) {
                 NotificationCenter.default.post(
@@ -82,7 +80,9 @@ class NCContextMenu: NSObject {
                                "error": NKError(),
                                "account": metadata.account])
             } else {
-                guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorOpenIn) else { return }
+                guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                               session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                               selector: NCGlobal.shared.selectorOpenIn) else { return }
                 hud.show(in: viewController.view)
                 NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
                 } requestHandler: { request in
@@ -90,12 +90,14 @@ class NCContextMenu: NSObject {
                 } progressHandler: { progress in
                     hud.progress = Float(progress.fractionCompleted)
                 } completion: { afError, error in
-                    if error == .success || afError?.isExplicitlyCancelledError ?? false {
-                        hud.dismiss()
-                    } else {
-                        hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                        hud.textLabel.text = error.description
-                        hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond)
+                    DispatchQueue.main.async {
+                        if error == .success || afError?.isExplicitlyCancelledError ?? false {
+                            hud.dismiss()
+                        } else {
+                            hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                            hud.textLabel.text = error.description
+                            hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond)
+                        }
                     }
                 }
             }
@@ -106,31 +108,10 @@ class NCContextMenu: NSObject {
             NCActionCenter.shared.openFileViewInFolder(serverUrl: metadata.serverUrl, fileNameBlink: metadata.fileName, fileNameOpen: nil)
         }
 
-        let save = UIAction(title: titleSave,
-                            image: UIImage(systemName: "square.and.arrow.down")) { _ in
+        let livePhotoSave = UIAction(title: NSLocalizedString("_livephoto_save_", comment: ""),
+                                     image: UIImage(systemName: "livephoto")) { _ in
             if let metadataMOV = metadataMOV {
                 NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
-            } else {
-                if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                    NCActionCenter.shared.saveAlbum(metadata: metadata)
-                } else {
-                    guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorSaveAlbum) else { return }
-                    hud.show(in: viewController.view)
-                    NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
-                    } requestHandler: { request in
-                        downloadRequest = request
-                    } progressHandler: { progress in
-                        hud.progress = Float(progress.fractionCompleted)
-                    } completion: { afError, error in
-                        if error == .success || afError?.isExplicitlyCancelledError ?? false {
-                            hud.dismiss()
-                        } else {
-                            hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                            hud.textLabel.text = error.description
-                            hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond)
-                        }
-                    }
-                }
             }
         }
 
@@ -145,7 +126,9 @@ class NCContextMenu: NSObject {
                                "error": NKError(),
                                "account": metadata.account])
             } else {
-                guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
+                guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                               session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                               selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
                 hud.show(in: viewController.view)
                 NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: false) {
                 } requestHandler: { request in
@@ -153,12 +136,14 @@ class NCContextMenu: NSObject {
                 } progressHandler: { progress in
                     hud.progress = Float(progress.fractionCompleted)
                 } completion: { afError, error in
-                    if error == .success || afError?.isExplicitlyCancelledError ?? false {
-                        hud.dismiss()
-                    } else {
-                        hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                        hud.textLabel.text = error.description
-                        hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond)
+                    DispatchQueue.main.async {
+                        if error == .success || afError?.isExplicitlyCancelledError ?? false {
+                            hud.dismiss()
+                        } else {
+                            hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                            hud.textLabel.text = error.description
+                            hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond)
+                        }
                     }
                 }
             }
@@ -228,9 +213,10 @@ class NCContextMenu: NSObject {
                 if metadata.isDocumentViewableOnly {
                     //
                 } else {
-                    menu.append(openIn)
-                    // SAVE CAMERA ROLL
-                    menu.append(save)
+                    menu.append(share)
+                    if NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) != nil {
+                        menu.append(livePhotoSave)
+                    }
                 }
             } else {
                 menu.append(favorite)
@@ -239,12 +225,15 @@ class NCContextMenu: NSObject {
                         menu.append(viewInFolder)
                     }
                 } else {
-                    menu.append(openIn)
-                    // SAVE CAMERA ROLL
-                    menu.append(save)
+                    menu.append(share)
+                    if NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) != nil {
+                        menu.append(livePhotoSave)
+                    }
+
                     if viewController is NCMedia {
                         menu.append(viewInFolder)
                     }
+
                     // MODIFY WITH QUICK LOOK
                     if metadata.isModifiableWithQuickLook {
                         menu.append(modify)

+ 2 - 0
iOSClient/Menu/NCMenu+FloatingPanel.swift

@@ -86,6 +86,8 @@ class NCMenuPanelController: FloatingPanelController {
 
         surfaceView.grabberHandle.accessibilityCustomActions = [collapseAction]
         surfaceView.grabberHandle.isAccessibilityElement = true
+
+        contentInsetAdjustmentBehavior = .never
     }
 
     @objc private func accessibilityActionCollapsePanel() {

+ 8 - 8
iOSClient/Menu/NCMenu.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dbT-V0-aXb">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dbT-V0-aXb">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -18,7 +18,7 @@
                         <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                         <prototypes>
                             <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="menuActionCell" rowHeight="60" id="MT1-Lu-9SA">
-                                <rect key="frame" x="0.0" y="44.5" width="414" height="60"/>
+                                <rect key="frame" x="0.0" y="50" width="414" height="60"/>
                                 <autoresizingMask key="autoresizingMask"/>
                                 <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="MT1-Lu-9SA" id="tmT-MO-Dwy">
                                     <rect key="frame" x="0.0" y="0.0" width="414" height="60"/>
@@ -32,16 +32,16 @@
                                             </constraints>
                                         </imageView>
                                         <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="6vv-IO-HJM">
-                                            <rect key="frame" x="60" y="19" width="326" height="22"/>
+                                            <rect key="frame" x="60" y="17" width="326" height="26"/>
                                             <subviews>
                                                 <label opaque="NO" userInteractionEnabled="NO" tag="2" contentMode="left" horizontalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="A8f-xF-j3i">
-                                                    <rect key="frame" x="0.0" y="0.0" width="326" height="20"/>
+                                                    <rect key="frame" x="0.0" y="0.0" width="326" height="20.5"/>
                                                     <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                     <nil key="textColor"/>
                                                     <nil key="highlightedColor"/>
                                                 </label>
                                                 <label opaque="NO" userInteractionEnabled="NO" tag="3" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="749" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SjQ-O4-Clh">
-                                                    <rect key="frame" x="0.0" y="22" width="326" height="0.0"/>
+                                                    <rect key="frame" x="0.0" y="22.5" width="326" height="3.5"/>
                                                     <fontDescription key="fontDescription" type="system" pointSize="15"/>
                                                     <color key="textColor" systemColor="secondaryLabelColor"/>
                                                     <nil key="highlightedColor"/>
@@ -54,8 +54,8 @@
                                         <constraint firstItem="6vv-IO-HJM" firstAttribute="leading" secondItem="RV0-3K-eSN" secondAttribute="trailing" constant="16" id="8tD-ZW-Y88"/>
                                         <constraint firstItem="RV0-3K-eSN" firstAttribute="leading" secondItem="tmT-MO-Dwy" secondAttribute="leading" constant="16" id="QQt-st-4hA"/>
                                         <constraint firstItem="RV0-3K-eSN" firstAttribute="centerY" secondItem="tmT-MO-Dwy" secondAttribute="centerY" id="R6O-om-tEz"/>
-                                        <constraint firstItem="6vv-IO-HJM" firstAttribute="top" secondItem="tmT-MO-Dwy" secondAttribute="topMargin" constant="8" id="d50-c3-Ofv"/>
-                                        <constraint firstAttribute="bottomMargin" secondItem="6vv-IO-HJM" secondAttribute="bottom" constant="8" id="zte-5x-B8K"/>
+                                        <constraint firstItem="6vv-IO-HJM" firstAttribute="top" secondItem="tmT-MO-Dwy" secondAttribute="topMargin" constant="6" id="d50-c3-Ofv"/>
+                                        <constraint firstAttribute="bottomMargin" secondItem="6vv-IO-HJM" secondAttribute="bottom" constant="6" id="zte-5x-B8K"/>
                                     </constraints>
                                 </tableViewCellContentView>
                             </tableViewCell>

+ 14 - 0
iOSClient/Menu/NCMenu.swift

@@ -50,6 +50,7 @@ class NCMenu: UITableViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
+        tableView.contentInset.top = 10
         tableView.estimatedRowHeight = 60
         tableView.rowHeight = UITableView.automaticDimension
         self.view.backgroundColor = menuColor
@@ -104,6 +105,19 @@ class NCMenu: UITableViewController {
             actionIconView?.image = action.icon
             actionNameLabel?.text = action.title
             actionNameLabel?.textColor = textColor
+            actionNameLabel?.lineBreakMode = .byTruncatingMiddle
+
+            if action.boldTitle {
+                actionNameLabel?.font = .systemFont(ofSize: 18, weight: .medium)
+            } else {
+                actionNameLabel?.font = .systemFont(ofSize: 18, weight: .regular)
+            }
+        }
+
+        if action.destructive {
+            actionIconView?.image = actionIconView?.image?.withRenderingMode(.alwaysTemplate)
+            actionIconView?.tintColor = .red
+            actionNameLabel?.textColor = .red
         }
 
         cell.accessoryType = action.selectable && action.selected ? .checkmark : .none

+ 24 - 46
iOSClient/Menu/NCMenuAction.swift

@@ -29,19 +29,23 @@ import JGProgressHUD
 
 class NCMenuAction {
     let title: String
+    let boldTitle: Bool
     let details: String?
     let icon: UIImage
     let selectable: Bool
     var onTitle: String?
     var onIcon: UIImage?
+    let destructive: Bool
     var selected: Bool = false
     var isOn: Bool = false
     var action: ((_ menuAction: NCMenuAction) -> Void)?
-    var rowHeight: CGFloat { self.title == NCMenuAction.seperatorIdentifier ? NCMenuAction.seperatorHeight : self.details != nil ? 80 : 60 }
+    var rowHeight: CGFloat { self.title == NCMenuAction.seperatorIdentifier ? NCMenuAction.seperatorHeight : self.details != nil ? 76 : 56 }
     var order: Int = 0
 
-    init(title: String, details: String? = nil, icon: UIImage, order: Int = 0, action: ((_ menuAction: NCMenuAction) -> Void)?) {
+    init(title: String, boldTitle: Bool = false, destructive: Bool = false, details: String? = nil, icon: UIImage, order: Int = 0, action: ((_ menuAction: NCMenuAction) -> Void)?) {
         self.title = title
+        self.boldTitle = boldTitle
+        self.destructive = destructive
         self.details = details
         self.icon = icon
         self.action = action
@@ -49,8 +53,10 @@ class NCMenuAction {
         self.order = order
     }
 
-    init(title: String, details: String? = nil, icon: UIImage, onTitle: String? = nil, onIcon: UIImage? = nil, selected: Bool, on: Bool, order: Int = 0, action: ((_ menuAction: NCMenuAction) -> Void)?) {
+    init(title: String, boldTitle: Bool = false, destructive: Bool = false, details: String? = nil, icon: UIImage, onTitle: String? = nil, onIcon: UIImage? = nil, selected: Bool, on: Bool, order: Int = 0, action: ((_ menuAction: NCMenuAction) -> Void)?) {
         self.title = title
+        self.boldTitle = boldTitle
+        self.destructive = destructive
         self.details = details
         self.icon = icon
         self.onTitle = onTitle ?? title
@@ -92,31 +98,25 @@ extension NCMenuAction {
         )
     }
 
-    /// Copy files to pasteboard
-    static func copyAction(selectOcId: [String], order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
-        NCMenuAction(
-            title: NSLocalizedString("_copy_file_", comment: ""),
-            icon: NCUtility().loadImage(named: "doc.on.doc"),
-            order: order,
-            action: { _ in
-                NCActionCenter.shared.copyPasteboard(pasteboardOcIds: selectOcId)
-                completion?()
-            }
-        )
-    }
-
     /// Delete files either from cache or from Nextcloud
     static func deleteAction(selectedMetadatas: [tableMetadata], indexPath: [IndexPath], metadataFolder: tableMetadata? = nil, viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         var titleDelete = NSLocalizedString("_delete_", comment: "")
+        var icon = "trash"
+        var destructive = false
+
         if selectedMetadatas.count > 1 {
             titleDelete = NSLocalizedString("_delete_selected_files_", comment: "")
+            destructive = true
         } else if let metadata = selectedMetadatas.first {
             if NCManageDatabase.shared.isMetadataShareOrMounted(metadata: metadata, metadataFolder: metadataFolder) {
                 titleDelete = NSLocalizedString("_leave_share_", comment: "")
+                icon = "person.2.slash"
             } else if metadata.directory {
                 titleDelete = NSLocalizedString("_delete_folder_", comment: "")
+                destructive = true
             } else {
                 titleDelete = NSLocalizedString("_delete_file_", comment: "")
+                destructive = true
             }
 
             if let metadataFolder = metadataFolder {
@@ -124,6 +124,7 @@ extension NCMenuAction {
                 let isMounted = metadata.permissions.contains(NCGlobal.shared.permissionMounted) && !metadataFolder.permissions.contains(NCGlobal.shared.permissionMounted)
                 if isShare || isMounted {
                     titleDelete = NSLocalizedString("_leave_share_", comment: "")
+                    icon = "person.2.slash"
                 }
             }
         } // else: no metadata selected
@@ -137,7 +138,8 @@ extension NCMenuAction {
 
         return NCMenuAction(
             title: titleDelete,
-            icon: NCUtility().loadImage(named: "trash"),
+            destructive: destructive,
+            icon: NCUtility().loadImage(named: icon),
             order: order,
             action: { _ in
                 let alertController = UIAlertController(
@@ -188,9 +190,9 @@ extension NCMenuAction {
     }
 
     /// Open "share view" (activity VC) to open files in another app
-    static func openInAction(selectedMetadatas: [tableMetadata], viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
+    static func share(selectedMetadatas: [tableMetadata], viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         NCMenuAction(
-            title: NSLocalizedString("_open_in_", comment: ""),
+            title: NSLocalizedString("_share_", comment: ""),
             icon: NCUtility().loadImage(named: "square.and.arrow.up"),
             order: order,
             action: { _ in
@@ -236,7 +238,7 @@ extension NCMenuAction {
     static func setAvailableOfflineAction(selectedMetadatas: [tableMetadata], isAnyOffline: Bool, viewController: UIViewController, order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         NCMenuAction(
             title: isAnyOffline ? NSLocalizedString("_remove_available_offline_", comment: "") : NSLocalizedString("_set_available_offline_", comment: ""),
-            icon: NCUtility().loadImage(named: "tray.and.arrow.down"),
+            icon: NCUtility().loadImage(named: "icloud.and.arrow.down"),
             order: order,
             action: { _ in
                 if !isAnyOffline, selectedMetadatas.count > 3 {
@@ -257,12 +259,11 @@ extension NCMenuAction {
             }
         )
     }
-
     /// Open view that lets the user move or copy the files within Nextcloud
     static func moveOrCopyAction(selectedMetadatas: [tableMetadata], indexPath: [IndexPath], order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         NCMenuAction(
-            title: NSLocalizedString("_move_or_copy_selected_files_", comment: ""),
-            icon: NCUtility().loadImage(named: "arrow.up.right.square"),
+            title: NSLocalizedString("_move_or_copy_", comment: ""),
+            icon: NCUtility().loadImage(named: "rectangle.portrait.and.arrow.right"),
             order: order,
             action: { _ in
                 NCActionCenter.shared.openSelectView(items: selectedMetadatas, indexPath: indexPath)
@@ -271,29 +272,6 @@ extension NCMenuAction {
         )
     }
 
-    /// Open AirPrint view to print a single file
-    static func printAction(metadata: tableMetadata, order: Int = 0) -> NCMenuAction {
-        NCMenuAction(
-            title: NSLocalizedString("_print_", comment: ""),
-            icon: NCUtility().loadImage(named: "printer"),
-            order: order,
-            action: { _ in
-                if NCUtilityFileSystem().fileProviderStorageExists(metadata) {
-                    NotificationCenter.default.post(
-                        name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
-                        object: nil,
-                        userInfo: ["ocId": metadata.ocId,
-                                   "selector": NCGlobal.shared.selectorPrint,
-                                   "error": NKError(),
-                                   "account": metadata.account])
-                } else {
-                    guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorPrint) else { return }
-                    NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
-                }
-            }
-        )
-    }
-
     /// Lock or unlock a file using *files_lock*
     static func lockUnlockFiles(shouldLock: Bool, metadatas: [tableMetadata], order: Int = 0, completion: (() -> Void)? = nil) -> NCMenuAction {
         let titleKey: String

+ 7 - 4
iOSClient/Menu/NCOperationSaveLivePhoto.swift

@@ -41,9 +41,12 @@ class NCOperationSaveLivePhoto: ConcurrentOperation {
 
     override func start() {
         guard !isCancelled,
-            let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: ""),
-            let metadataLive = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: self.metadataMOV.ocId, selector: "") else { return self.finish() }
-
+            let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                     session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                     selector: ""),
+            let metadataLive = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadataMOV],
+                                                                                         session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                         selector: "") else { return self.finish() }
         DispatchQueue.main.async {
             self.hud.indicatorView = JGProgressHUDRingIndicatorView()
             if let indicatorView = self.hud.indicatorView as? JGProgressHUDRingIndicatorView {
@@ -67,7 +70,7 @@ class NCOperationSaveLivePhoto: ConcurrentOperation {
                 }
                 return self.finish()
             }
-            NCNetworking.shared.download(metadata: metadataLive, withNotificationProgressTask: false, checkfileProviderStorageExists: true) {
+            NCNetworking.shared.download(metadata: metadataLive, withNotificationProgressTask: false) {
                 DispatchQueue.main.async {
                     self.hud.textLabel.text = NSLocalizedString("_download_video_", comment: "")
                     self.hud.detailTextLabel.text = self.metadataMOV.fileName

+ 3 - 3
iOSClient/Menu/NCTrash+Menu.swift

@@ -34,7 +34,7 @@ extension NCTrash {
                 title: NSLocalizedString("_cancel_", comment: ""),
                 icon: utility.loadImage(named: "xmark"),
                 action: { _ in
-                    self.tapSelect()
+                    self.toggleSelect()
                 }
             ),
             NCMenuAction(
@@ -51,7 +51,7 @@ extension NCTrash {
                 icon: utility.loadImage(named: "restore"),
                 action: { _ in
                     self.selectOcId.forEach(self.restoreItem)
-                    self.tapSelect()
+                    self.toggleSelect()
                 }
             ),
             NCMenuAction(
@@ -61,7 +61,7 @@ extension NCTrash {
                     let alert = UIAlertController(title: NSLocalizedString("_trash_delete_selected_", comment: ""), message: "", preferredStyle: .alert)
                     alert.addAction(UIAlertAction(title: NSLocalizedString("_delete_", comment: ""), style: .destructive, handler: { _ in
                         self.selectOcId.forEach(self.deleteItem)
-                        self.tapSelect()
+                        self.toggleSelect()
                     }))
                     alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: { _ in }))
                     self.present(alert, animated: true, completion: nil)

+ 26 - 67
iOSClient/Menu/NCViewer+Menu.swift

@@ -32,8 +32,7 @@ extension NCViewer {
         guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) else { return }
 
         var actions = [NCMenuAction]()
-        var titleFavorite = NSLocalizedString("_add_favorites_", comment: "")
-        if metadata.favorite { titleFavorite = NSLocalizedString("_remove_favorites_", comment: "") }
+
         let localFile = NCManageDatabase.shared.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
         let isOffline = localFile?.offline == true
 
@@ -44,7 +43,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_details_", comment: ""),
-                    icon: utility.loadImage(named: "info"),
+                    icon: utility.loadImage(named: "info.circle"),
                     action: { _ in
                         NCActionCenter.shared.openShare(viewController: viewController, metadata: metadata, page: .activity)
                     }
@@ -74,8 +73,8 @@ extension NCViewer {
         if !metadata.lock {
             actions.append(
                 NCMenuAction(
-                    title: titleFavorite,
-                    icon: utility.loadImage(named: "star.fill", color: NCBrandColor.shared.yellowFavorite),
+                    title: metadata.favorite ? NSLocalizedString("_remove_favorites_", comment: "") : NSLocalizedString("_add_favorites_", comment: ""),
+                    icon: utility.loadImage(named: metadata.favorite ? "star.slash.fill" : "star.fill", color: NCBrandColor.shared.yellowFavorite),
                     action: { _ in
                         NCNetworking.shared.favoriteMetadata(metadata) { error in
                             if error != .success {
@@ -90,70 +89,31 @@ extension NCViewer {
         //
         // OFFLINE
         //
-        if !webView, metadata.isSettableOnOffline {
+        if !webView, metadata.canSetAsAvailableOffline {
             actions.append(.setAvailableOfflineAction(selectedMetadatas: [metadata], isAnyOffline: isOffline, viewController: viewController))
         }
 
         //
-        // OPEN IN
+        // SHARE
         //
         if !webView, metadata.canOpenIn {
-            actions.append(.openInAction(selectedMetadatas: [metadata], viewController: viewController))
+            actions.append(.share(selectedMetadatas: [metadata], viewController: viewController))
         }
 
         //
-        // PRINT
+        // SAVE LIVE PHOTO
         //
-        if !webView, metadata.isPrintable {
+        if let metadataMOV = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
             actions.append(
                 NCMenuAction(
-                    title: NSLocalizedString("_print_", comment: ""),
-                    icon: utility.loadImage(named: "printer"),
+                    title: NSLocalizedString("_livephoto_save_", comment: ""),
+                    icon: NCUtility().loadImage(named: "livephoto"),
                     action: { _ in
-                        if self.utilityFileSystem.fileProviderStorageExists(metadata) {
-                            NotificationCenter.default.post(
-                                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
-                                object: nil,
-                                userInfo: ["ocId": metadata.ocId,
-                                           "selector": NCGlobal.shared.selectorPrint,
-                                           "error": NKError(),
-                                           "account": metadata.account])
-                        } else {
-                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorPrint) else { return }
-                            NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
-                        }
-                    }
-                )
-            )
-        }
-
-        //
-        // CONVERSION VIDEO TO MPEG4 (MFFF Lib)
-        //
-        /*
-#if MFFFLIB
-        if metadata.isVideo {
-            
-            actions.append(
-                NCMenuAction(
-                    title: NSLocalizedString("_video_processing_", comment: ""),
-                    icon: utility.loadImage(named: "film"),
-                    action: { menuAction in
-                        if let ncplayer = (viewController as? NCViewerMediaPage)?.currentViewController.ncplayer {
-                            ncplayer.convertVideo(withAlert: false)
-                        }
+                        NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV))
                     }
                 )
             )
         }
-#endif
-        */
-        //
-        // SAVE CAMERA ROLL
-        //
-        if !webView, metadata.isSavebleInCameraRoll {
-            actions.append(.saveMediaAction(selectedMediaMetadatas: [metadata]))
-        }
 
         //
         // SAVE AS SCAN
@@ -162,7 +122,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_save_as_scan_", comment: ""),
-                    icon: utility.loadImage(named: "viewfinder.circle"),
+                    icon: utility.loadImage(named: "doc.viewfinder"),
                     action: { _ in
                         if self.utilityFileSystem.fileProviderStorageExists(metadata) {
                             NotificationCenter.default.post(
@@ -173,7 +133,9 @@ extension NCViewer {
                                            "error": NKError(),
                                            "account": metadata.account])
                         } else {
-                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorSaveAsScan) else { return }
+                            guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                                           session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                                           selector: NCGlobal.shared.selectorSaveAsScan) else { return }
                             NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }
@@ -188,7 +150,7 @@ extension NCViewer {
             actions.append(
                 NCMenuAction(
                     title: NSLocalizedString("_rename_", comment: ""),
-                    icon: utility.loadImage(named: "pencil"),
+                    icon: utility.loadImage(named: "text.cursor"),
                     action: { _ in
 
                         if let vcRename = UIStoryboard(name: "NCRenameFile", bundle: nil).instantiateInitialViewController() as? NCRenameFile {
@@ -215,22 +177,17 @@ extension NCViewer {
         }
 
         //
-        // COPY IN PASTEBOARD
-        //
-        if !webView, metadata.isCopyableInPasteboard {
-            actions.append(.copyAction(selectOcId: [metadata.ocId]))
-        }
-
-        //
-        // DOWNLOAD LOCALLY
+        // DOWNLOAD FULL RESOLUTION
         //
         if !webView, metadata.session.isEmpty, !self.utilityFileSystem.fileProviderStorageExists(metadata) {
             actions.append(
                 NCMenuAction(
-                    title: NSLocalizedString("_download_locally_", comment: ""),
-                    icon: utility.loadImage(named: "icloud.and.arrow.down"),
+                    title: NSLocalizedString("_try_download_full_resolution_", comment: ""),
+                    icon: utility.loadImage(named: "photo"),
                     action: { _ in
-                        guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: "") else { return }
+                        guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                                       session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                                       selector: "") else { return }
                         NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                     }
                 )
@@ -280,7 +237,9 @@ extension NCViewer {
                                            "error": NKError(),
                                            "account": metadata.account])
                         } else {
-                            guard let metadata = NCManageDatabase.shared.setMetadataSessionInWaitDownload(ocId: metadata.ocId, selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
+                            guard let metadata = NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                                                           session: NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload,
+                                                                                                           selector: NCGlobal.shared.selectorLoadFileQuickLook) else { return }
                             NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
                         }
                     }

+ 0 - 2
iOSClient/NCGlobal.swift

@@ -300,7 +300,6 @@ class NCGlobal: NSObject {
     let selectorListingFavorite                     = "listingFavorite"
     let selectorLoadFileView                        = "loadFileView"
     let selectorLoadFileQuickLook                   = "loadFileQuickLook"
-    let selectorLoadOffline                         = "loadOffline"
     let selectorOpenIn                              = "openIn"
     let selectorPrint                               = "print"
     let selectorUploadAutoUpload                    = "uploadAutoUpload"
@@ -312,7 +311,6 @@ class NCGlobal: NSObject {
     let selectorSaveAsScan                          = "saveAsScan"
     let selectorOpenDetail                          = "openDetail"
     let selectorSynchronizationOffline              = "synchronizationOffline"
-    let selectorSynchronizationFavorite             = "synchronizationFavorite"
 
     // Metadata : Status
     //

+ 1 - 2
iOSClient/NCImageCache.swift

@@ -117,8 +117,7 @@ import RealmSwift
             }
         }
 
-        let endDate = Date()
-        let diffDate = endDate.timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate
+        let diffDate = Date().timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate
         NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
         NextcloudKit.shared.nkCommonInstance.writeLog("Counter process: \(cache.count)")
         NextcloudKit.shared.nkCommonInstance.writeLog("Time process: \(diffDate)")

+ 5 - 5
iOSClient/Networking/NCAutoUpload.swift

@@ -168,6 +168,7 @@ class NCAutoUpload: NSObject {
                     metadata.session = session
                     metadata.sessionSelector = selector
                     metadata.status = NCGlobal.shared.metadataStatusWaitUpload
+                    metadata.sessionDate = Date()
                     if assetMediaType == PHAssetMediaType.video {
                         metadata.classFile = NKCommon.TypeClassFile.video.rawValue
                     } else if assetMediaType == PHAssetMediaType.image {
@@ -184,7 +185,7 @@ class NCAutoUpload: NSObject {
             self.endForAssetToUpload = true
 
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start createProcessUploads")
-            NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: metadatas, completion: completion)
+            NCNetworkingProcess.shared.createProcessUploads(metadatas: metadatas, completion: completion)
         }
     }
 
@@ -207,10 +208,9 @@ class NCAutoUpload: NSObject {
 
         NCAskAuthorization().askAuthorizationPhotoLibrary(viewController: viewController) { hasPermission in
 
+            guard hasPermission else { return completion(nil) }
             let assetCollection = PHAssetCollection.fetchAssetCollections(with: PHAssetCollectionType.smartAlbum, subtype: PHAssetCollectionSubtype.smartAlbumUserLibrary, options: nil)
-            // swiftlint:disable empty_count
-            guard hasPermission, assetCollection.count != 0 else { return completion(nil) }
-            // swiftlint:enable empty_count
+            guard let assetCollection = assetCollection.firstObject else { return completion(nil) }
 
             let predicateImage = NSPredicate(format: "mediaType == %i", PHAssetMediaType.image.rawValue)
             let predicateVideo = NSPredicate(format: "mediaType == %i", PHAssetMediaType.video.rawValue)
@@ -229,7 +229,7 @@ class NCAutoUpload: NSObject {
             }
 
             fetchOptions.predicate = predicate
-            let assets: PHFetchResult<PHAsset> = PHAsset.fetchAssets(in: assetCollection.firstObject!, options: fetchOptions)
+            let assets: PHFetchResult<PHAsset> = PHAsset.fetchAssets(in: assetCollection, options: fetchOptions)
 
             if selector == NCGlobal.shared.selectorUploadAutoUpload {
                 let idAssets = NCManageDatabase.shared.getPhotoLibraryIdAsset(image: account.autoUploadImage, video: account.autoUploadVideo, account: account.account)

+ 355 - 0
iOSClient/Networking/NCNetworking+Download.swift

@@ -0,0 +1,355 @@
+//
+//  NCNetworking+Download.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 07/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import JGProgressHUD
+import NextcloudKit
+import Alamofire
+import Queuer
+
+extension NCNetworking {
+
+    func download(metadata: tableMetadata,
+                  withNotificationProgressTask: Bool,
+                  hudView: UIView? = nil,
+                  hud: JGProgressHUD? = nil,
+                  start: @escaping () -> Void = { },
+                  requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
+                  progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
+                  completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
+
+        if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload {
+            downloadFile(metadata: metadata, withNotificationProgressTask: withNotificationProgressTask, hudView: hudView, hud: hud) {
+                start()
+            } requestHandler: { request in
+                requestHandler(request)
+            } progressHandler: { progress in
+                progressHandler(progress)
+            } completion: { afError, error in
+                completion(afError, error)
+            }
+        } else {
+            downloadFileInBackground(metadata: metadata, start: start, completion: { afError, error in
+                completion(afError, error)
+            })
+        }
+    }
+
+    private func downloadFile(metadata: tableMetadata,
+                              withNotificationProgressTask: Bool,
+                              hudView: UIView?,
+                              hud: JGProgressHUD?,
+                              start: @escaping () -> Void = { },
+                              requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
+                              progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
+                              completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
+
+        guard !metadata.isInTransfer else { return completion(nil, NKError()) }
+        var downloadTask: URLSessionTask?
+        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
+        let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
+
+        if NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) == nil {
+            NCManageDatabase.shared.addMetadata(metadata)
+        }
+
+        NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, options: options, requestHandler: { request in
+
+            self.downloadRequest[fileNameLocalPath] = request
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       status: NCGlobal.shared.metadataStatusDownloading)
+            requestHandler(request)
+
+        }, taskHandler: { task in
+
+            downloadTask = task
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       taskIdentifier: task.taskIdentifier)
+
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
+                                            object: nil,
+                                            userInfo: ["ocId": metadata.ocId,
+                                                       "serverUrl": metadata.serverUrl,
+                                                       "account": metadata.account])
+            start()
+
+        }, progressHandler: { progress in
+
+            if withNotificationProgressTask, Int(floor(progress.fractionCompleted * 100)).isMultiple(of: 5) {
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
+                                                object: nil,
+                                                userInfo: ["account": metadata.account,
+                                                           "ocId": metadata.ocId,
+                                                           "fileName": metadata.fileName,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
+                                                           "progress": NSNumber(value: progress.fractionCompleted),
+                                                           "totalBytes": NSNumber(value: progress.totalUnitCount),
+                                                           "totalBytesExpected": NSNumber(value: progress.completedUnitCount)])
+            }
+            progressHandler(progress)
+
+        }) { _, etag, date, length, allHeaderFields, afError, error in
+
+            var error = error
+            self.downloadRequest.removeValue(forKey: fileNameLocalPath)
+
+            var dateLastModified: NSDate?
+            if let downloadTask = downloadTask {
+                if let header = allHeaderFields, let dateString = header["Last-Modified"] as? String {
+                    dateLastModified = NextcloudKit.shared.nkCommonInstance.convertDate(dateString, format: "EEE, dd MMM y HH:mm:ss zzz")
+                }
+                if afError?.isExplicitlyCancelledError ?? false {
+                    error = NKError(errorCode: NCGlobal.shared.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
+                }
+                self.downloadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, task: downloadTask, error: error)
+            }
+            completion(afError, error)
+        }
+    }
+
+    private func downloadFileInBackground(metadata: tableMetadata,
+                                          start: @escaping () -> Void = { },
+                                          requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
+                                          progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
+                                          completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
+
+        let session: URLSession = sessionManagerDownloadBackground
+        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+
+        start()
+
+        if let task = nkBackground.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, session: session) {
+
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Download file \(metadata.fileNameView) with task with taskIdentifier \(task.taskIdentifier)")
+
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       status: NCGlobal.shared.metadataStatusDownloading,
+                                                       taskIdentifier: task.taskIdentifier)
+
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
+                                            object: nil,
+                                            userInfo: ["ocId": metadata.ocId,
+                                                       "serverUrl": metadata.serverUrl,
+                                                       "account": metadata.account])
+            completion(nil, NKError())
+
+        } else {
+
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       session: "",
+                                                       sessionError: "",
+                                                       selector: "",
+                                                       status: NCGlobal.shared.metadataStatusNormal)
+            completion(nil, NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: "task null"))
+        }
+    }
+
+    func downloadComplete(fileName: String,
+                          serverUrl: String,
+                          etag: String?,
+                          date: NSDate?,
+                          dateLastModified: NSDate?,
+                          length: Int64,
+                          fileNameLocalPath: String?,
+                          task: URLSessionTask,
+                          error: NKError) {
+
+        DispatchQueue.global().async {
+
+            guard let metadata = NCManageDatabase.shared.getMetadataFromFileNameLocalPath(fileNameLocalPath) else { return }
+
+            if error == .success {
+
+#if !EXTENSION
+                if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
+                    NCEndToEndEncryption.sharedManager()?.decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
+                }
+#endif
+                NCManageDatabase.shared.addLocalFile(metadata: metadata)
+                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                           session: "",
+                                                           sessionError: "",
+                                                           status: NCGlobal.shared.metadataStatusNormal,
+                                                           etag: etag)
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                                                object: nil,
+                                                userInfo: ["ocId": metadata.ocId,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "account": metadata.account,
+                                                           "selector": metadata.sessionSelector,
+                                                           "error": error])
+
+            } else if error.errorCode == NSURLErrorCancelled || error.errorCode == NCGlobal.shared.errorRequestExplicityCancelled {
+
+                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                           session: "",
+                                                           sessionError: "",
+                                                           selector: "",
+                                                           status: NCGlobal.shared.metadataStatusNormal)
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
+                                                object: nil,
+                                                userInfo: ["ocId": metadata.ocId,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "account": metadata.account])
+
+            } else {
+
+                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                           session: "",
+                                                           sessionError: "",
+                                                           selector: "",
+                                                           status: NCGlobal.shared.metadataStatusNormal)
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
+                                                object: nil,
+                                                userInfo: ["ocId": metadata.ocId,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "account": metadata.account,
+                                                           "selector": metadata.sessionSelector,
+                                                           "error": error])
+            }
+
+            self.downloadMetadataInBackground.removeValue(forKey: FileNameServerUrl(fileName: fileName, serverUrl: serverUrl))
+            self.delegate?.downloadComplete?(fileName: fileName, serverUrl: serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, task: task, error: error)
+        }
+    }
+
+    func downloadProgress(_ progress: Float,
+                          totalBytes: Int64,
+                          totalBytesExpected: Int64,
+                          fileName: String,
+                          serverUrl: String,
+                          session: URLSession,
+                          task: URLSessionTask) {
+
+        DispatchQueue.global().async {
+
+            self.delegate?.downloadProgress?(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
+
+            var metadata: tableMetadata?
+
+            if let metadataTmp = self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] {
+                metadata = metadataTmp
+            } else if let metadataTmp = NCManageDatabase.shared.getMetadataFromFileName(fileName, serverUrl: serverUrl) {
+                self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] = metadataTmp
+                metadata = metadataTmp
+            }
+
+            if let metadata = metadata, Int(floor(progress * 100)).isMultiple(of: 5) {
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
+                                                object: nil,
+                                                userInfo: ["account": metadata.account,
+                                                           "ocId": metadata.ocId,
+                                                           "fileName": metadata.fileName,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
+                                                           "progress": NSNumber(value: progress),
+                                                           "totalBytes": NSNumber(value: totalBytes),
+                                                           "totalBytesExpected": NSNumber(value: totalBytesExpected)])
+            }
+        }
+    }
+
+#if !EXTENSION
+    func downloadAvatar(user: String,
+                        dispalyName: String?,
+                        fileName: String,
+                        cell: NCCellProtocol,
+                        view: UIView?,
+                        cellImageView: UIImageView?) {
+
+        let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
+
+        if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
+            cellImageView?.image = image
+            cell.fileAvatarImageView?.image = image
+            return
+        }
+
+        if let account = NCManageDatabase.shared.getActiveAccount() {
+            cellImageView?.image = utility.loadUserImage(for: user, displayName: dispalyName, userBaseUrl: account)
+        }
+
+        for case let operation as NCOperationDownloadAvatar in downloadAvatarQueue.operations where operation.fileName == fileName { return }
+        downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view, cellImageView: cellImageView))
+    }
+#endif
+
+    func cancelDownloadTasks() {
+
+        downloadRequest.removeAll()
+        let sessionManager = NextcloudKit.shared.sessionManager
+        sessionManager.session.getTasksWithCompletionHandler { _, _, downloadTasks in
+            downloadTasks.forEach {
+                $0.cancel()
+            }
+        }
+        if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload)) {
+            NCManageDatabase.shared.clearMetadataSession(metadatas: results)
+        }
+    }
+
+    func cancelDownloadBackgroundTask() {
+
+        Task {
+            let tasksBackground = await NCNetworking.shared.sessionManagerDownloadBackground.tasks
+            for task in tasksBackground.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
+                task.cancel()
+            }
+            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NCNetworking.shared.sessionDownloadBackground)) {
+                NCManageDatabase.shared.clearMetadataSession(metadatas: results)
+            }
+        }
+    }
+}
+
+class NCOperationDownload: ConcurrentOperation {
+
+    var metadata: tableMetadata
+    var selector: String
+
+    init(metadata: tableMetadata, selector: String) {
+        self.metadata = tableMetadata.init(value: metadata)
+        self.selector = selector
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
+        metadata.sessionError = ""
+        metadata.sessionSelector = selector
+        metadata.sessionTaskIdentifier = 0
+        metadata.status = NCGlobal.shared.metadataStatusWaitDownload
+
+        NCManageDatabase.shared.addMetadata(metadata)
+
+        NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
+        } completion: { _, _ in
+            self.finish()
+        }
+    }
+}

+ 116 - 0
iOSClient/Networking/NCNetworking+LivePhoto.swift

@@ -0,0 +1,116 @@
+//
+//  NCNetworking+LivePhoto.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 07/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import JGProgressHUD
+import NextcloudKit
+import Alamofire
+import Queuer
+
+extension NCNetworking {
+
+    func uploadLivePhoto(metadata: tableMetadata, userInfo aUserInfo: [AnyHashable: Any]) {
+
+        guard let metadata1 = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND urlBase == %@ AND path == %@ AND fileName == %@", metadata.account, metadata.urlBase, metadata.path, metadata.livePhotoFile)) else {
+            metadata.livePhotoFile = ""
+            NCManageDatabase.shared.addMetadata(metadata)
+            return NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedLivePhoto),
+                                                   object: nil,
+                                                   userInfo: aUserInfo)
+        }
+        if metadata1.status != NCGlobal.shared.metadataStatusNormal { return }
+
+        Task {
+            let serverUrlfileNamePath = metadata.urlBase + metadata.path + metadata.fileName
+            var livePhotoFile = metadata1.fileId
+            let results = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: livePhotoFile)
+            if results.error == .success {
+                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata.account, ocId: metadata.ocId, livePhotoFile: livePhotoFile)
+            } else {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Uplod set LivePhoto with error \(results.error.errorCode)")
+            }
+
+            let serverUrlfileNamePath1 = metadata1.urlBase + metadata1.path + metadata1.fileName
+            livePhotoFile = metadata.fileId
+            let results1 = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath1, livePhotoFile: livePhotoFile)
+            if results1.error == .success {
+                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata1.account, ocId: metadata1.ocId, livePhotoFile: livePhotoFile)
+            } else {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Upload set LivePhoto with error \(results.error.errorCode)")
+            }
+            if results.error == .success, results1.error == .success {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[SUCCESS] Upload set LivePhoto for files " + (metadata.fileName as NSString).deletingPathExtension)
+
+            }
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedLivePhoto),
+                                            object: nil,
+                                            userInfo: aUserInfo)
+        }
+    }
+
+    func convertLivePhoto(metadata: tableMetadata) {
+
+        guard metadata.status == NCGlobal.shared.metadataStatusNormal else { return }
+
+        let account = metadata.account
+        let livePhotoFile = metadata.livePhotoFile
+        let serverUrlfileNamePath = metadata.urlBase + metadata.path + metadata.fileName
+        let ocId = metadata.ocId
+
+        DispatchQueue.global().async {
+            if let result = NCManageDatabase.shared.getResultMetadata(predicate: NSPredicate(format: "account == '\(account)' AND status == \(NCGlobal.shared.metadataStatusNormal) AND (fileName == '\(livePhotoFile)' || fileId == '\(livePhotoFile)')")) {
+                if livePhotoFile == result.fileId { return }
+                for case let operation as NCOperationConvertLivePhoto in self.convertLivePhotoQueue.operations where operation.serverUrlfileNamePath == serverUrlfileNamePath { continue }
+                self.convertLivePhotoQueue.addOperation(NCOperationConvertLivePhoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: result.fileId, account: account, ocId: ocId))
+            }
+        }
+    }
+}
+
+class NCOperationConvertLivePhoto: ConcurrentOperation {
+
+    var serverUrlfileNamePath, livePhotoFile, account, ocId: String
+
+    init(serverUrlfileNamePath: String, livePhotoFile: String, account: String, ocId: String) {
+        self.serverUrlfileNamePath = serverUrlfileNamePath
+        self.livePhotoFile = livePhotoFile
+        self.account = account
+        self.ocId = ocId
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+        NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: livePhotoFile, options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, error in
+            if error == .success {
+                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: self.account, ocId: self.ocId, livePhotoFile: self.livePhotoFile)
+            } else {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Convert LivePhoto with error \(error.errorCode)")
+            }
+            self.finish()
+            if NCNetworking.shared.convertLivePhotoQueue.operationCount == 0 {
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, second: 0.1)
+            }
+        }
+    }
+}

+ 94 - 0
iOSClient/Networking/NCNetworking+Synchronization.swift

@@ -0,0 +1,94 @@
+//
+//  NCNetworking+Synchronization.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 07/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import JGProgressHUD
+import NextcloudKit
+import Alamofire
+
+extension NCNetworking {
+
+    func synchronization(account: String,
+                         serverUrl: String,
+                         add: Bool,
+                         completion: @escaping (_ errorCode: Int, _ items: Int) -> Void = { _, _ in }) {
+
+        let startDate = Date()
+        let options = NKRequestOptions(timeout: 120, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
+        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl,
+                                             depth: "infinity",
+                                             showHiddenFiles: NCKeychain().showHiddenFiles,
+                                             options: options) { resultAccount, files, _, error in
+
+            guard account == resultAccount else { return }
+            var metadatasDirectory: [tableMetadata] = []
+            var metadatasSynchronizationOffline: [tableMetadata] = []
+            if !add {
+                if NCManageDatabase.shared.getResultMetadata(predicate: NSPredicate(format: "account == %@ AND sessionSelector == %@ AND (status == %d OR status == %d)", account, NCGlobal.shared.selectorSynchronizationOffline, NCGlobal.shared.metadataStatusWaitDownload, NCGlobal.shared.metadataStatusDownloading)) != nil { return }
+            }
+
+            if error == .success {
+                for file in files {
+                    if file.directory {
+                        metadatasDirectory.append(NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: false))
+                    } else if self.isSynchronizable(ocId: file.ocId, fileName: file.fileName, etag: file.etag) {
+                        metadatasSynchronizationOffline.append(NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: false))
+                    }
+                }
+                NCManageDatabase.shared.addMetadatas(metadatasDirectory)
+                NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: metadatasSynchronizationOffline,
+                                                                          session: NCNetworking.shared.sessionDownloadBackground,
+                                                                          selector: NCGlobal.shared.selectorSynchronizationOffline)
+                NCManageDatabase.shared.setDirectorySynchronizationDate(serverUrl: serverUrl, account: account)
+                let diffDate = Date().timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate
+                NextcloudKit.shared.nkCommonInstance.writeLog("[LOG] Synchronization \(serverUrl) in \(diffDate)")
+                completion(0, metadatasSynchronizationOffline.count)
+            } else {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Synchronization \(serverUrl), \(error.errorCode), \(error.description)")
+                completion(error.errorCode, metadatasSynchronizationOffline.count)
+            }
+        }
+    }
+
+    @discardableResult
+    func synchronization(account: String, serverUrl: String, add: Bool) async -> (Int, Int) {
+        await withUnsafeContinuation({ continuation in
+            synchronization(account: account, serverUrl: serverUrl, add: add) { errorCode, items in
+                continuation.resume(returning: (errorCode, items))
+            }
+        })
+    }
+
+    func isSynchronizable(ocId: String, fileName: String, etag: String) -> Bool {
+        if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId),
+           (metadata.status == NCGlobal.shared.metadataStatusDownloading || metadata.status == NCGlobal.shared.metadataStatusWaitDownload) {
+            return false
+        }
+        let localFile = NCManageDatabase.shared.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", ocId))?.first
+        if localFile?.etag != etag || NCUtilityFileSystem().fileProviderStorageSize(ocId, fileNameView: fileName) == 0 {
+            return true
+        } else {
+            return false
+        }
+    }
+}

+ 550 - 0
iOSClient/Networking/NCNetworking+Upload.swift

@@ -0,0 +1,550 @@
+//
+//  NCNetworking+Upload.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 07/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import JGProgressHUD
+import NextcloudKit
+import Alamofire
+
+extension NCNetworking {
+
+    func upload(metadata: tableMetadata,
+                uploadE2EEDelegate: uploadE2EEDelegate? = nil,
+                hudView: UIView?,
+                hud: JGProgressHUD?,
+                start: @escaping () -> Void = { },
+                requestHandler: @escaping (_ request: UploadRequest) -> Void = { _ in },
+                progressHandler: @escaping (_ totalBytesExpected: Int64, _ totalBytes: Int64, _ fractionCompleted: Double) -> Void = { _, _, _ in },
+                completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
+
+        let metadata = tableMetadata.init(value: metadata)
+        var numChunks: Int = 0
+        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Upload file \(metadata.fileNameView) with Identifier \(metadata.assetLocalIdentifier) with size \(metadata.size) [CHUNK \(metadata.chunk), E2EE \(metadata.isDirectoryE2EE)]")
+
+        if metadata.isDirectoryE2EE {
+#if !EXTENSION_FILE_PROVIDER_EXTENSION && !EXTENSION_WIDGET
+            Task {
+                let error = await NCNetworkingE2EEUpload().upload(metadata: metadata, uploadE2EEDelegate: uploadE2EEDelegate, hudView: hudView, hud: hud)
+                completion(nil, error)
+            }
+#endif
+        } else if metadata.chunk > 0 {
+                if let hudView {
+                    DispatchQueue.main.async {
+                        if let hud {
+                            hud.indicatorView = JGProgressHUDRingIndicatorView()
+                            if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
+                                indicatorView.ringWidth = 1.5
+                            }
+                            hud.tapOnHUDViewBlock = { _ in
+                                NotificationCenter.default.postOnMainThread(name: "NextcloudKit.chunkedFile.stop")
+                            }
+                            hud.textLabel.text = NSLocalizedString("_wait_file_preparation_", comment: "")
+                            hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
+                            hud.show(in: hudView)
+                        }
+                    }
+                }
+            uploadChunkFile(metadata: metadata) { num in
+                numChunks = num
+            } counterChunk: { counter in
+                DispatchQueue.main.async { hud?.progress = Float(counter) / Float(numChunks) }
+            } start: {
+                DispatchQueue.main.async { hud?.dismiss() }
+            } completion: { account, _, afError, error in
+                DispatchQueue.main.async { hud?.dismiss() }
+                var sessionTaskFailedCode = 0
+                let directory = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)
+                if let error = NextcloudKit.shared.nkCommonInstance.getSessionErrorFromAFError(afError) {
+                    sessionTaskFailedCode = error.code
+                }
+                switch error.errorCode {
+                case NKError.chunkNoEnoughMemory, NKError.chunkCreateFolder, NKError.chunkFilesNull, NKError.chunkFileNull:
+                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                    NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
+                    NCContentPresenter().messageNotification("_error_files_upload_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error, afterDelay: 0.5)
+                case NKError.chunkFileUpload:
+                    if let afError, (afError.isExplicitlyCancelledError || sessionTaskFailedCode == NCGlobal.shared.errorExplicitlyCancelled ) {
+                        NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
+                    }
+                case NKError.chunkMoveFile:
+                    NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
+                    NCContentPresenter().messageNotification("_chunk_move_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error, afterDelay: 0.5)
+                default: break
+                }
+                completion(afError, error)
+            }
+        } else if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload {
+            let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+            uploadFile(metadata: metadata, fileNameLocalPath: fileNameLocalPath, start: start, progressHandler: progressHandler) { _, _, _, _, _, _, afError, error in
+                completion(afError, error)
+            }
+        } else {
+            uploadFileInBackground(metadata: metadata, start: start) { error in
+                completion(nil, error)
+            }
+        }
+    }
+
+    func uploadFile(metadata: tableMetadata,
+                    fileNameLocalPath: String,
+                    withUploadComplete: Bool = true,
+                    customHeaders: [String: String]? = nil,
+                    start: @escaping () -> Void = { },
+                    requestHandler: @escaping (_ request: UploadRequest) -> Void = { _ in },
+                    progressHandler: @escaping (_ totalBytesExpected: Int64, _ totalBytes: Int64, _ fractionCompleted: Double) -> Void = { _, _, _ in },
+                    completion: @escaping (_ account: String, _ ocId: String?, _ etag: String?, _ date: NSDate?, _ size: Int64, _ allHeaderFields: [AnyHashable: Any]?, _ afError: AFError?, _ error: NKError) -> Void) {
+
+        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+        var uploadTask: URLSessionTask?
+        let options = NKRequestOptions(customHeader: customHeaders, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
+
+        NextcloudKit.shared.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: metadata.creationDate as Date, dateModificationFile: metadata.date as Date, options: options, requestHandler: { request in
+
+            self.uploadRequest[fileNameLocalPath] = request
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       status: NCGlobal.shared.metadataStatusUploading)
+            requestHandler(request)
+
+        }, taskHandler: { task in
+
+            uploadTask = task
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       taskIdentifier: task.taskIdentifier)
+
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile),
+                                            object: nil,
+                                            userInfo: ["ocId": metadata.ocId,
+                                                       "serverUrl": metadata.serverUrl,
+                                                       "account": metadata.account,
+                                                       "fileName": metadata.fileName,
+                                                       "sessionSelector": metadata.sessionSelector])
+            start()
+
+        }, progressHandler: { progress in
+
+            if Int(floor(progress.fractionCompleted * 100)).isMultiple(of: 5) {
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
+                                                object: nil,
+                                                userInfo: ["account": metadata.account,
+                                                           "ocId": metadata.ocId,
+                                                           "fileName": metadata.fileName,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "status": NSNumber(value: NCGlobal.shared.metadataStatusUploading),
+                                                           "progress": NSNumber(value: progress.fractionCompleted),
+                                                           "totalBytes": NSNumber(value: progress.totalUnitCount),
+                                                           "totalBytesExpected": NSNumber(value: progress.completedUnitCount)])
+            }
+            progressHandler(progress.completedUnitCount, progress.totalUnitCount, progress.fractionCompleted)
+
+        }) { account, ocId, etag, date, size, allHeaderFields, afError, error in
+
+            var error = error
+            self.uploadRequest.removeValue(forKey: fileNameLocalPath)
+            if withUploadComplete, let uploadTask = uploadTask {
+                if afError?.isExplicitlyCancelledError ?? false {
+                    error = NKError(errorCode: NCGlobal.shared.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
+                }
+                self.uploadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, ocId: ocId, etag: etag, date: date, size: size, fileNameLocalPath: fileNameLocalPath, task: uploadTask, error: error)
+            }
+            completion(account, ocId, etag, date, size, allHeaderFields, afError, error)
+        }
+    }
+
+    func uploadChunkFile(metadata: tableMetadata,
+                         withUploadComplete: Bool = true,
+                         customHeaders: [String: String]? = nil,
+                         numChunks: @escaping (_ num: Int) -> Void = { _ in },
+                         counterChunk: @escaping (_ counter: Int) -> Void = { _ in },
+                         start: @escaping () -> Void = { },
+                         progressHandler: @escaping (_ totalBytesExpected: Int64, _ totalBytes: Int64, _ fractionCompleted: Double) -> Void = { _, _, _ in },
+                         completion: @escaping (_ account: String, _ file: NKFile?, _ afError: AFError?, _ error: NKError) -> Void) {
+
+        let directory = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)
+        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+        let chunkFolder = NCManageDatabase.shared.getChunkFolder(account: metadata.account, ocId: metadata.ocId)
+        let filesChunk = NCManageDatabase.shared.getChunks(account: metadata.account, ocId: metadata.ocId)
+        var uploadTask: URLSessionTask?
+
+        var chunkSize = NCGlobal.shared.chunkSizeMBCellular
+        if NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi {
+            chunkSize = NCGlobal.shared.chunkSizeMBEthernetOrWiFi
+        }
+        let options = NKRequestOptions(customHeader: customHeaders, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
+
+        NextcloudKit.shared.uploadChunk(directory: directory, fileName: metadata.fileName, date: metadata.date as Date, creationDate: metadata.creationDate as Date, serverUrl: metadata.serverUrl, chunkFolder: chunkFolder, filesChunk: filesChunk, chunkSize: chunkSize, options: options) { num in
+
+            numChunks(num)
+
+        } counterChunk: { counter in
+
+            counterChunk(counter)
+
+        } start: { filesChunk in
+
+            start()
+            NCManageDatabase.shared.addChunks(account: metadata.account, ocId: metadata.ocId, chunkFolder: chunkFolder, filesChunk: filesChunk)
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile),
+                                            object: nil,
+                                            userInfo: ["ocId": metadata.ocId,
+                                                       "serverUrl": metadata.serverUrl,
+                                                       "account": metadata.account,
+                                                       "fileName": metadata.fileName,
+                                                       "sessionSelector": metadata.sessionSelector])
+
+        } requestHandler: { request in
+
+            self.uploadRequest[fileNameLocalPath] = request
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       status: NCGlobal.shared.metadataStatusUploading)
+
+        } taskHandler: { task in
+
+            uploadTask = task
+            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                       taskIdentifier: task.taskIdentifier)
+
+        } progressHandler: { totalBytesExpected, totalBytes, fractionCompleted in
+
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
+                                            object: nil,
+                                            userInfo: ["account": metadata.account,
+                                                       "ocId": metadata.ocId,
+                                                       "fileName": metadata.fileName,
+                                                       "serverUrl": metadata.serverUrl,
+                                                       "status": NSNumber(value: NCGlobal.shared.metadataStatusUploading),
+                                                       "chunk": metadata.chunk,
+                                                       "e2eEncrypted": metadata.e2eEncrypted,
+                                                       "progress": NSNumber(value: fractionCompleted),
+                                                       "totalBytes": NSNumber(value: totalBytes),
+                                                       "totalBytesExpected": NSNumber(value: totalBytesExpected)])
+
+            progressHandler(totalBytesExpected, totalBytes, fractionCompleted)
+
+        } uploaded: { fileChunk in
+
+            NCManageDatabase.shared.deleteChunk(account: metadata.account, ocId: metadata.ocId, fileChunk: fileChunk, directory: directory)
+
+        } completion: { account, _, file, afError, error in
+
+            self.uploadRequest.removeValue(forKey: fileNameLocalPath)
+            if error == .success {
+                NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
+            }
+            if withUploadComplete, let uploadTask {
+                self.uploadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, ocId: file?.ocId, etag: file?.etag, date: file?.date, size: file?.size ?? 0, fileNameLocalPath: fileNameLocalPath, task: uploadTask, error: error)
+            }
+            completion(account, file, afError, error)
+        }
+    }
+
+    private func uploadFileInBackground(metadata: tableMetadata,
+                                        start: @escaping () -> Void = { },
+                                        completion: @escaping (_ error: NKError) -> Void) {
+
+        var session: URLSession?
+        let metadata = tableMetadata.init(value: metadata)
+        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+
+        if metadata.session == sessionUploadBackground || metadata.session == sessionUploadBackgroundExtension {
+            session = sessionManagerUploadBackground
+        } else if metadata.session == sessionUploadBackgroundWWan {
+            session = sessionManagerUploadBackgroundWWan
+        }
+
+        start()
+
+        // Check file dim > 0
+        if utilityFileSystem.getFileSize(filePath: fileNameLocalPath) == 0 && metadata.size != 0 {
+
+            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+            completion(NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: NSLocalizedString("_error_not_found_", value: "The requested resource could not be found", comment: "")))
+
+        } else {
+
+            if let task = nkBackground.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: metadata.creationDate as Date, dateModificationFile: metadata.date as Date, session: session!) {
+
+                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Upload file \(metadata.fileNameView) with task with taskIdentifier \(task.taskIdentifier)")
+
+                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                           status: NCGlobal.shared.metadataStatusUploading,
+                                                           taskIdentifier: task.taskIdentifier)
+
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile),
+                                                object: nil,
+                                                userInfo: ["ocId": metadata.ocId,
+                                                           "serverUrl": metadata.serverUrl,
+                                                           "account": metadata.account,
+                                                           "fileName": metadata.fileName,
+                                                           "sessionSelector": metadata.sessionSelector])
+                completion(NKError())
+
+            } else {
+
+                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                completion(NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: "task null"))
+            }
+        }
+    }
+
+    func uploadComplete(fileName: String,
+                        serverUrl: String,
+                        ocId: String?,
+                        etag: String?,
+                        date: NSDate?,
+                        size: Int64,
+                        fileNameLocalPath: String?,
+                        task: URLSessionTask,
+                        error: NKError) {
+
+        var isApplicationStateActive = false
+#if !EXTENSION
+        isApplicationStateActive = UIApplication.shared.applicationState == .active
+#endif
+
+        DispatchQueue.global().async {
+
+            guard let metadata = NCManageDatabase.shared.getMetadataFromFileNameLocalPath(fileNameLocalPath) else { return }
+            let ocIdTemp = metadata.ocId
+            let selector = metadata.sessionSelector
+
+            if error == .success, let ocId = ocId, size == metadata.size {
+
+                let metadata = tableMetadata.init(value: metadata)
+
+                metadata.uploadDate = date ?? NSDate()
+                metadata.etag = etag ?? ""
+                metadata.ocId = ocId
+                metadata.chunk = 0
+
+                if let fileId = self.utility.ocIdToFileId(ocId: ocId) {
+                    metadata.fileId = fileId
+                }
+
+                metadata.session = ""
+                metadata.sessionError = ""
+                metadata.status = NCGlobal.shared.metadataStatusNormal
+
+                // Delete Asset on Photos album
+                if NCKeychain().removePhotoCameraRoll, !metadata.assetLocalIdentifier.isEmpty {
+                    metadata.deleteAssetLocalIdentifier = true
+                }
+
+                NCManageDatabase.shared.addMetadata(metadata)
+                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", ocIdTemp))
+
+                if selector == NCGlobal.shared.selectorUploadFileNODelete {
+                    self.utilityFileSystem.moveFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp), toPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(ocId))
+                    NCManageDatabase.shared.addLocalFile(metadata: metadata)
+                } else {
+                    self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp))
+                }
+
+                NextcloudKit.shared.nkCommonInstance.writeLog("[SUCCESS] Upload complete " + serverUrl + "/" + fileName + ", result: success(\(size) bytes)")
+
+                let userInfo: [AnyHashable: Any] = ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "fileName": metadata.fileName, "ocIdTemp": ocIdTemp, "error": error]
+                if metadata.isLivePhoto, NCGlobal.shared.isLivePhotoServerAvailable {
+                    self.uploadLivePhoto(metadata: metadata, userInfo: userInfo)
+                } else {
+                    NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
+                                                    object: nil,
+                                                    userInfo: userInfo)
+                }
+            } else {
+
+                if error.errorCode == NSURLErrorCancelled || error.errorCode == NCGlobal.shared.errorRequestExplicityCancelled {
+
+                    self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                    NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
+                                                    object: nil,
+                                                    userInfo: ["ocId": metadata.ocId,
+                                                               "serverUrl": metadata.serverUrl,
+                                                               "account": metadata.account])
+
+                } else if error.errorCode == NCGlobal.shared.errorBadRequest || error.errorCode == NCGlobal.shared.errorUnsupportedMediaType {
+
+                    self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                    NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
+                                                    object: nil,
+                                                    userInfo: ["ocId": metadata.ocId,
+                                                               "serverUrl": metadata.serverUrl,
+                                                               "account": metadata.account])
+                    if isApplicationStateActive {
+                        NCContentPresenter().showError(error: NKError(errorCode: error.errorCode, errorDescription: "_virus_detect_"))
+                    }
+
+                    // Client Diagnostic
+                    NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueVirusDetected)
+
+                } else if error.errorCode == NCGlobal.shared.errorForbidden && isApplicationStateActive {
+#if !EXTENSION
+                    DispatchQueue.main.async {
+                        let newFileName = self.utilityFileSystem.createFileName(metadata.fileName, serverUrl: metadata.serverUrl, account: metadata.account)
+                        let alertController = UIAlertController(title: error.errorDescription, message: NSLocalizedString("_change_upload_filename_", comment: ""), preferredStyle: .alert)
+                        alertController.addAction(UIAlertAction(title: String(format: NSLocalizedString("_save_file_as_", comment: ""), newFileName), style: .default, handler: { _ in
+                            let atpath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName
+                            let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + newFileName
+                            self.utilityFileSystem.moveFile(atPath: atpath, toPath: toPath)
+                            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                                       newFileName: newFileName,
+                                                                       sessionError: "",
+                                                                       status: NCGlobal.shared.metadataStatusWaitUpload,
+                                                                       errorCode: error.errorCode)
+                        }))
+                        alertController.addAction(UIAlertAction(title: NSLocalizedString("_discard_changes_", comment: ""), style: .destructive, handler: { _ in
+                            self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+                            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
+                                                            object: nil,
+                                                            userInfo: ["ocId": metadata.ocId,
+                                                                       "serverUrl": metadata.serverUrl,
+                                                                       "account": metadata.account])
+                        }))
+
+                        let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+                        appDelegate.window?.rootViewController?.present(alertController, animated: true)
+
+                        // Client Diagnostic
+                        NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueProblems, error: NCGlobal.shared.diagnosticProblemsForbidden)
+                    }
+#endif
+                } else {
+
+                    NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                               sessionError: error.errorDescription,
+                                                               status: NCGlobal.shared.metadataStatusUploadError,
+                                                               errorCode: error.errorCode)
+                    NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
+                                                    object: nil,
+                                                    userInfo: ["ocId": metadata.ocId,
+                                                               "serverUrl": metadata.serverUrl,
+                                                               "account": metadata.account,
+                                                               "fileName": metadata.fileName,
+                                                               "ocIdTemp": ocIdTemp,
+                                                               "error": error])
+
+                    // Client Diagnostic
+                    if error.errorCode == NCGlobal.shared.errorInternalServerError {
+                        NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueProblems, error: NCGlobal.shared.diagnosticProblemsBadResponse)
+                    } else {
+                        NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueProblems, error: NCGlobal.shared.diagnosticProblemsUploadServerError)
+                    }
+                }
+            }
+
+            self.uploadMetadataInBackground.removeValue(forKey: FileNameServerUrl(fileName: fileName, serverUrl: serverUrl))
+            self.delegate?.uploadComplete?(fileName: fileName, serverUrl: serverUrl, ocId: ocId, etag: etag, date: date, size: size, fileNameLocalPath: fileNameLocalPath, task: task, error: error)
+        }
+    }
+
+    func uploadProgress(_ progress: Float,
+                        totalBytes: Int64,
+                        totalBytesExpected: Int64,
+                        fileName: String,
+                        serverUrl: String,
+                        session: URLSession,
+                        task: URLSessionTask) {
+
+        DispatchQueue.global().async {
+
+            self.delegate?.uploadProgress?(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
+
+            var metadata: tableMetadata?
+
+            if let metadataTmp = self.uploadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] {
+                metadata = metadataTmp
+            } else if let metadataTmp = NCManageDatabase.shared.getMetadataFromFileName(fileName, serverUrl: serverUrl) {
+                self.uploadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] = metadataTmp
+                metadata = metadataTmp
+            }
+
+            if let metadata = metadata, Int(floor(progress * 100)).isMultiple(of: 5) {
+                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
+                                                object: nil,
+                                                userInfo: ["account": metadata.account,
+                                                           "ocId": metadata.ocId,
+                                                           "fileName": metadata.fileName,
+                                                           "serverUrl": serverUrl,
+                                                           "status": NSNumber(value: NCGlobal.shared.metadataStatusUploading),
+                                                           "chunk": metadata.chunk,
+                                                           "e2eEncrypted": metadata.e2eEncrypted,
+                                                           "progress": NSNumber(value: progress),
+                                                           "totalBytes": NSNumber(value: totalBytes),
+                                                           "totalBytesExpected": NSNumber(value: totalBytesExpected)])
+            }
+        }
+    }
+
+    func getUploadBackgroundSession(queue: DispatchQueue = .main,
+                                    completion: @escaping (_ filesNameLocalPath: [String]) -> Void) {
+
+        var filesNameLocalPath: [String] = []
+
+        sessionManagerUploadBackground.getAllTasks(completionHandler: { tasks in
+            for task in tasks {
+                filesNameLocalPath.append(task.description)
+            }
+            self.sessionManagerUploadBackgroundWWan.getAllTasks(completionHandler: { tasks in
+                for task in tasks {
+                    filesNameLocalPath.append(task.description)
+                }
+                queue.async { completion(filesNameLocalPath) }
+            })
+        })
+    }
+
+    func cancelUploadTasks() {
+
+        uploadRequest.removeAll()
+        let sessionManager = NextcloudKit.shared.sessionManager
+        sessionManager.session.getTasksWithCompletionHandler { _, uploadTasks, _ in
+            uploadTasks.forEach {
+                $0.cancel()
+            }
+        }
+
+        if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status > 0 AND session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload)) {
+            NCManageDatabase.shared.deleteMetadata(results: results)
+        }
+    }
+
+    func cancelUploadBackgroundTask() {
+
+        Task {
+            let tasksBackground = await NCNetworking.shared.sessionManagerUploadBackground.tasks
+            for task in tasksBackground.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
+                task.cancel()
+            }
+            let tasksBackgroundWWan = await NCNetworking.shared.sessionManagerUploadBackgroundWWan.tasks
+            for task in tasksBackgroundWWan.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
+                task.cancel()
+            }
+            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status > 0 AND (session == %@ || session == %@)", NCNetworking.shared.sessionUploadBackground, NCNetworking.shared.sessionUploadBackgroundWWan)) {
+                NCManageDatabase.shared.deleteMetadata(results: results)
+            }
+        }
+    }
+}

+ 985 - 0
iOSClient/Networking/NCNetworking+WebDAV.swift

@@ -0,0 +1,985 @@
+//
+//  NCNetworking+WebDAV.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 07/02/24.
+//  Copyright © 2024 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import UIKit
+import JGProgressHUD
+import NextcloudKit
+import Alamofire
+import Queuer
+import Photos
+
+extension NCNetworking {
+
+    // MARK: - Read file, folder
+
+    func readFolder(serverUrl: String,
+                    account: String,
+                    forceReplaceMetadatas: Bool = false,
+                    completion: @escaping (_ account: String, _ metadataFolder: tableMetadata?, _ metadatas: [tableMetadata]?, _ metadatasChangedCount: Int, _ metadatasChanged: Bool, _ error: NKError) -> Void) {
+
+        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl,
+                                             depth: "1",
+                                             showHiddenFiles: NCKeychain().showHiddenFiles,
+                                             includeHiddenFiles: NCGlobal.shared.includeHiddenFiles,
+                                             options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
+
+            guard error == .success else {
+                return completion(account, nil, nil, 0, false, error)
+            }
+
+            NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: true) { metadataFolder, metadatasFolder, metadatas in
+
+                // Add metadata folder
+                NCManageDatabase.shared.addMetadata(tableMetadata.init(value: metadataFolder))
+
+                // Update directory
+                NCManageDatabase.shared.addDirectory(encrypted: metadataFolder.e2eEncrypted, favorite: metadataFolder.favorite, ocId: metadataFolder.ocId, fileId: metadataFolder.fileId, etag: metadataFolder.etag, permissions: metadataFolder.permissions, serverUrl: serverUrl, account: metadataFolder.account)
+                NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, richWorkspace: metadataFolder.richWorkspace, account: metadataFolder.account)
+
+                // Update sub directories NO Update richWorkspace
+                for metadata in metadatasFolder {
+                    let serverUrl = metadata.serverUrl + "/" + metadata.fileName
+                    NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrl, account: account)
+                }
+
+#if !EXTENSION
+                // Convert Live Photo
+                for metadata in metadatas {
+                    if NCGlobal.shared.isLivePhotoServerAvailable, metadata.isLivePhoto {
+                        NCNetworking.shared.convertLivePhoto(metadata: metadata)
+                    }
+                }
+#endif
+
+                let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)
+
+                if forceReplaceMetadatas {
+                    NCManageDatabase.shared.replaceMetadata(metadatas, predicate: predicate)
+                    completion(account, metadataFolder, metadatas, 0, true, error)
+                } else {
+                    let results = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
+                    completion(account, metadataFolder, metadatas, results.metadatasChangedCount, results.metadatasChanged, error)
+                }
+            }
+        }
+    }
+
+    func readFile(serverUrlFileName: String,
+                  showHiddenFiles: Bool = NCKeychain().showHiddenFiles,
+                  queue: DispatchQueue = NextcloudKit.shared.nkCommonInstance.backgroundQueue,
+                  completion: @escaping (_ account: String, _ metadata: tableMetadata?, _ error: NKError) -> Void) {
+
+        let options = NKRequestOptions(queue: queue)
+
+        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0", showHiddenFiles: showHiddenFiles, options: options) { account, files, _, error in
+            guard error == .success, files.count == 1, let file = files.first else {
+                completion(account, nil, error)
+                return
+            }
+
+            let isDirectoryE2EE = self.utilityFileSystem.isDirectoryE2EE(file: file)
+            let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
+
+            completion(account, metadata, error)
+        }
+    }
+
+    func fileExists(serverUrlFileName: String,
+                    completion: @escaping (_ account: String, _ exists: Bool?, _ file: NKFile?, _ error: NKError) -> Void) {
+
+        let requestBody =
+        """
+        <?xml version=\"1.0\" encoding=\"UTF-8\"?>
+        <d:propfind xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">
+            <d:prop></d:prop>
+        </d:propfind>
+        """
+
+        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName,
+                                             depth: "0",
+                                             requestBody: requestBody.data(using: .utf8),
+                                             options: NKRequestOptions(timeout: 10, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
+
+            if error == .success, let file = files.first {
+                completion(account, true, file, error)
+            } else if error.errorCode == NCGlobal.shared.errorResourceNotFound {
+                completion(account, false, nil, error)
+            } else {
+                completion(account, nil, nil, error)
+            }
+        }
+    }
+
+    func fileExists(serverUrlFileName: String) async -> (account: String, exists: Bool?, file: NKFile?, error: NKError) {
+
+        await withUnsafeContinuation({ continuation in
+            fileExists(serverUrlFileName: serverUrlFileName) { account, exists, file, error in
+                continuation.resume(returning: (account, exists, file, error))
+            }
+        })
+    }
+
+    func createFileName(fileNameBase: String, account: String, serverUrl: String) async -> String {
+
+        var exitLoop = false
+        var resultFileName = fileNameBase
+
+        func newFileName() {
+            var name = NSString(string: resultFileName).deletingPathExtension
+            let ext = NSString(string: resultFileName).pathExtension
+            let characters = Array(name)
+            if characters.count < 2 {
+                if ext.isEmpty {
+                    resultFileName = name + " 1"
+                } else {
+                    resultFileName = name + " 1" + "." + ext
+                }
+            } else {
+                let space = characters[characters.count - 2]
+                let numChar = characters[characters.count - 1]
+                var num = Int(String(numChar))
+                if space == " " && num != nil {
+                    name = String(name.dropLast())
+                    num = num! + 1
+                    if ext.isEmpty {
+                        resultFileName = name + "\(num!)"
+                    } else {
+                        resultFileName = name + "\(num!)" + "." + ext
+                    }
+                } else {
+                    if ext.isEmpty {
+                        resultFileName = name + " 1"
+                    } else {
+                        resultFileName = name + " 1" + "." + ext
+                    }
+                }
+            }
+        }
+
+        while !exitLoop {
+            if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "fileNameView == %@ AND serverUrl == %@ AND account == %@", resultFileName, serverUrl, account)) != nil {
+                newFileName()
+                continue
+            }
+            let results = await fileExists(serverUrlFileName: serverUrl + "/" + resultFileName)
+            if let exists = results.exists, exists {
+                newFileName()
+            } else {
+                exitLoop = true
+            }
+        }
+        return resultFileName
+    }
+
+    // MARK: - Create Folder
+
+    func createFolder(fileName: String,
+                      serverUrl: String,
+                      account: String,
+                      urlBase: String,
+                      userId: String,
+                      overwrite: Bool = false,
+                      withPush: Bool,
+                      completion: @escaping (_ error: NKError) -> Void) {
+
+        let isDirectoryEncrypted = utilityFileSystem.isDirectoryE2EE(account: account, urlBase: urlBase, userId: userId, serverUrl: serverUrl)
+        let fileName = fileName.trimmingCharacters(in: .whitespacesAndNewlines)
+
+        if isDirectoryEncrypted {
+#if !EXTENSION
+            Task {
+                let error = await NCNetworkingE2EECreateFolder().createFolder(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId, withPush: withPush)
+                completion(error)
+            }
+#endif
+        } else {
+            createFolderPlain(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, overwrite: overwrite, withPush: withPush, completion: completion)
+        }
+    }
+
+    private func createFolderPlain(fileName: String,
+                                   serverUrl: String,
+                                   account: String,
+                                   urlBase: String,
+                                   overwrite: Bool,
+                                   withPush: Bool,
+                                   completion: @escaping (_ error: NKError) -> Void) {
+
+        var fileNameFolder = utility.removeForbiddenCharacters(fileName)
+        if fileName != fileNameFolder {
+            let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), NCGlobal.shared.forbiddenCharacters.joined(separator: " "))
+            let error = NKError(errorCode: NCGlobal.shared.errorConflict, errorDescription: errorDescription)
+            return completion(error)
+        }
+
+        if !overwrite {
+            fileNameFolder = utilityFileSystem.createFileName(fileNameFolder, serverUrl: serverUrl, account: account)
+        }
+        if fileNameFolder.isEmpty {
+            return completion(NKError())
+        }
+        let fileNameFolderUrl = serverUrl + "/" + fileNameFolder
+
+        NextcloudKit.shared.createFolder(serverUrlFileName: fileNameFolderUrl) { account, _, _, error in
+            guard error == .success else {
+                if error.errorCode == NCGlobal.shared.errorMethodNotSupported && overwrite {
+                    completion(NKError())
+                } else {
+                    completion(error)
+                }
+                return
+            }
+
+            self.readFile(serverUrlFileName: fileNameFolderUrl) { account, metadataFolder, error in
+
+                if error == .success {
+                    if let metadata = metadataFolder {
+                        NCManageDatabase.shared.addMetadata(metadata)
+                        NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: fileNameFolderUrl, account: account)
+                    }
+                    if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadataFolder?.ocId) {
+                        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCreateFolder, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "withPush": withPush])
+                    }
+                }
+                completion(error)
+            }
+        }
+    }
+
+    func createFolder(assets: [PHAsset],
+                      selector: String,
+                      useSubFolder: Bool,
+                      account: String,
+                      urlBase: String,
+                      userId: String,
+                      withPush: Bool) -> Bool {
+
+        let autoUploadPath = NCManageDatabase.shared.getAccountAutoUploadPath(urlBase: urlBase, userId: userId, account: account)
+        let serverUrlBase = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: urlBase, userId: userId, account: account)
+        let fileNameBase = NCManageDatabase.shared.getAccountAutoUploadFileName()
+        let autoUploadSubfolderGranularity = NCManageDatabase.shared.getAccountAutoUploadSubfolderGranularity()
+
+        func createFolder(fileName: String, serverUrl: String) -> Bool {
+            var result: Bool = false
+            let semaphore = DispatchSemaphore(value: 0)
+            NCNetworking.shared.createFolder(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId, overwrite: true, withPush: withPush) { error in
+                if error == .success { result = true }
+                semaphore.signal()
+            }
+            semaphore.wait()
+            return result
+        }
+
+        func createNameSubFolder() -> [String] {
+
+            var datesSubFolder: [String] = []
+            let dateFormatter = DateFormatter()
+
+            for asset in assets {
+                let date = asset.creationDate ?? Date()
+                dateFormatter.dateFormat = "yyyy"
+                let year = dateFormatter.string(from: date)
+                dateFormatter.dateFormat = "MM"
+                let month = dateFormatter.string(from: date)
+                dateFormatter.dateFormat = "dd"
+                let day = dateFormatter.string(from: date)
+                if autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityYearly {
+                    datesSubFolder.append("\(year)")
+                } else if autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily {
+                    datesSubFolder.append("\(year)/\(month)/\(day)")
+                } else {  // Month Granularity is default
+                    datesSubFolder.append("\(year)/\(month)")
+                }
+            }
+
+            return Array(Set(datesSubFolder))
+        }
+
+        var result = createFolder(fileName: fileNameBase, serverUrl: serverUrlBase)
+
+        if useSubFolder && result {
+            for dateSubFolder in createNameSubFolder() {
+                let subfolderArray = dateSubFolder.split(separator: "/")
+                let year = subfolderArray[0]
+                let serverUrlYear = autoUploadPath
+                result = createFolder(fileName: String(year), serverUrl: serverUrlYear)  // Year always present independently of preference value
+                if result && autoUploadSubfolderGranularity >= NCGlobal.shared.subfolderGranularityMonthly {
+                    let month = subfolderArray[1]
+                    let serverUrlMonth = autoUploadPath + "/" + year
+                    result = createFolder(fileName: String(month), serverUrl: serverUrlMonth)
+                    if result && autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily {
+                        let day = subfolderArray[2]
+                        let serverUrlDay = autoUploadPath + "/" + year + "/" + month
+                        result = createFolder(fileName: String(day), serverUrl: serverUrlDay)
+                    }
+                }
+                if !result { break }
+            }
+        }
+
+        return result
+    }
+
+    // MARK: - Delete
+
+    func deleteMetadata(_ metadata: tableMetadata, onlyLocalCache: Bool) async -> (NKError) {
+
+        if onlyLocalCache {
+
+#if !EXTENSION
+            NCActivityIndicator.shared.start()
+#endif
+
+            func delete(metadata: tableMetadata) {
+                if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+                    NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "account == %@ AND ocId == %@", metadataLive.account, metadataLive.ocId))
+                    utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
+                }
+                NCManageDatabase.shared.deleteVideo(metadata: metadata)
+                NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "account == %@ AND ocId == %@", metadata.account, metadata.ocId))
+                utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+            }
+
+            if metadata.directory {
+                let serverUrl = metadata.serverUrl + "/" + metadata.fileName
+                if let metadatas = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == false", metadata.account, serverUrl)) {
+                    for metadata in metadatas {
+                        delete(metadata: metadata)
+                    }
+                }
+            } else {
+                delete(metadata: metadata)
+            }
+
+#if !EXTENSION
+            NCActivityIndicator.shared.stop()
+#endif
+            return NKError()
+        }
+
+        if metadata.isDirectoryE2EE {
+#if !EXTENSION
+            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+                let error = await NCNetworkingE2EEDelete().delete(metadata: metadataLive)
+                if error == .success {
+                    return await NCNetworkingE2EEDelete().delete(metadata: metadata)
+                } else {
+                    return error
+                }
+            } else {
+                return await NCNetworkingE2EEDelete().delete(metadata: metadata)
+            }
+#else
+            return NKError()
+#endif
+        } else {
+            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
+                let error = await deleteMetadataPlain(metadataLive)
+                if error == .success {
+                    return await deleteMetadataPlain(metadata)
+                } else {
+                    return error
+                }
+            } else {
+                return await deleteMetadataPlain(metadata)
+            }
+        }
+    }
+
+    func deleteMetadataPlain(_ metadata: tableMetadata, customHeader: [String: String]? = nil) async -> NKError {
+
+        // verify permission
+        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanDelete)
+        if !metadata.permissions.isEmpty && permission == false {
+            return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_delete_file_")
+        }
+        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+        let options = NKRequestOptions(customHeader: customHeader)
+
+        let result = await NextcloudKit.shared.deleteFileOrFolder(serverUrlFileName: serverUrlFileName, options: options)
+        if result.error == .success || result.error.errorCode == NCGlobal.shared.errorResourceNotFound {
+
+            do {
+                try FileManager.default.removeItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+            } catch { }
+
+            NCManageDatabase.shared.deleteVideo(metadata: metadata)
+            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+            NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+            // LIVE PHOTO SERVER
+            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
+                do {
+                    try FileManager.default.removeItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
+                } catch { }
+
+                NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
+                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
+                NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
+            }
+
+            if metadata.directory {
+                NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: metadata.account)
+            }
+        }
+        return result.error
+    }
+
+    // MARK: - Rename
+
+    func renameMetadata(_ metadata: tableMetadata,
+                        fileNameNew: String,
+                        indexPath: IndexPath,
+                        viewController: UIViewController?,
+                        completion: @escaping (_ error: NKError) -> Void) {
+
+        let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata)
+        let fileNameNew = fileNameNew.trimmingCharacters(in: .whitespacesAndNewlines)
+        let fileNameNewLive = (fileNameNew as NSString).deletingPathExtension + ".mov"
+
+        if metadata.isDirectoryE2EE {
+#if !EXTENSION
+            Task {
+                if let metadataLive = metadataLive {
+                    let error = await NCNetworkingE2EERename().rename(metadata: metadataLive, fileNameNew: fileNameNew, indexPath: indexPath)
+                    if error == .success {
+                        let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
+                        DispatchQueue.main.async { completion(error) }
+                    } else {
+                        DispatchQueue.main.async { completion(error) }
+                    }
+                } else {
+                    let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
+                    DispatchQueue.main.async { completion(error) }
+                }
+            }
+#endif
+        } else {
+            if let metadataLive, metadata.isNotFlaggedAsLivePhotoByServer {
+                renameMetadataPlain(metadataLive, fileNameNew: fileNameNewLive, indexPath: indexPath) { error in
+                    if error == .success {
+                        self.renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
+                    } else {
+                        completion(error)
+                    }
+                }
+            } else {
+                renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
+            }
+        }
+    }
+
+    private func renameMetadataPlain(_ metadata: tableMetadata,
+                                     fileNameNew: String,
+                                     indexPath: IndexPath,
+                                     completion: @escaping (_ error: NKError) -> Void) {
+
+        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanRename)
+        if !metadata.permissions.isEmpty && !permission {
+            return completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_"))
+        }
+        let fileName = utility.removeForbiddenCharacters(fileNameNew)
+        if fileName != fileNameNew {
+            let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), NCGlobal.shared.forbiddenCharacters.joined(separator: " "))
+            let error = NKError(errorCode: NCGlobal.shared.errorConflict, errorDescription: errorDescription)
+            return completion(error)
+        }
+        let fileNameNew = fileName
+        if fileNameNew.isEmpty || fileNameNew == metadata.fileNameView {
+            return completion(NKError())
+        }
+        let fileNamePath = metadata.serverUrl + "/" + metadata.fileName
+        let fileNameToPath = metadata.serverUrl + "/" + fileNameNew
+
+        NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: fileNamePath, serverUrlFileNameDestination: fileNameToPath, overwrite: false) { _, error in
+            if error == .success {
+                NCManageDatabase.shared.renameMetadata(fileNameTo: fileNameNew, ocId: metadata.ocId)
+                if metadata.directory {
+                    let serverUrl = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
+                    let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: fileNameNew)
+                    if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) {
+                        NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, serverUrlTo: serverUrlTo, etag: "", ocId: nil, fileId: nil, encrypted: directory.e2eEncrypted, richWorkspace: nil, account: metadata.account)
+                    }
+                } else {
+                    if (metadata.fileName as NSString).pathExtension != (fileNameNew as NSString).pathExtension {
+                        let path = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)
+                        self.utilityFileSystem.removeFile(atPath: path)
+                    } else {
+                        NCManageDatabase.shared.setLocalFile(ocId: metadata.ocId, fileName: fileNameNew)
+                        let atPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName
+                        let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + fileNameNew
+                        self.utilityFileSystem.moveFile(atPath: atPath, toPath: toPath)
+                    }
+                }
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["ocId": metadata.ocId, "account": metadata.account, "indexPath": indexPath])
+            }
+            completion(error)
+        }
+    }
+
+    // MARK: - Move
+
+    func moveMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
+
+        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
+            let error = await moveMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
+            if error == .success {
+                return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
+            } else {
+                return error
+            }
+        }
+        return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
+    }
+
+    private func moveMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
+
+        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanRename)
+        if !metadata.permissions.isEmpty && !permission {
+            return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
+        }
+        let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
+        let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
+
+        let result = await NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite)
+        if result.error == .success {
+            if metadata.directory {
+                NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: result.account)
+            } else {
+                do {
+                    try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
+                } catch { }
+                NCManageDatabase.shared.deleteVideo(metadata: metadata)
+                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
+                // LIVE PHOTO SERVER
+                if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
+                    do {
+                        try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
+                    } catch { }
+                    NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
+                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
+                    NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
+                }
+            }
+        }
+        return result.error
+    }
+
+    // MARK: - Copy
+
+    func copyMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
+
+        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
+            let error = await copyMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
+            if error == .success {
+                return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
+            } else {
+                return error
+            }
+        }
+        return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
+    }
+
+    private func copyMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
+
+        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanRename)
+        if !metadata.permissions.isEmpty && !permission {
+            return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
+        }
+        let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
+        let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
+
+        let result = await NextcloudKit.shared.copyFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite)
+        return result.error
+    }
+
+    // MARK: - Favorite
+
+    func favoriteMetadata(_ metadata: tableMetadata,
+                          completion: @escaping (_ error: NKError) -> Void) {
+
+        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
+            favoriteMetadataPlain(metadataLive) { error in
+                if error == .success {
+                    self.favoriteMetadataPlain(metadata, completion: completion)
+                } else {
+                    completion(error)
+                }
+            }
+        } else {
+            favoriteMetadataPlain(metadata, completion: completion)
+        }
+    }
+
+    private func favoriteMetadataPlain(_ metadata: tableMetadata,
+                                       completion: @escaping (_ error: NKError) -> Void) {
+
+        let fileName = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
+        let favorite = !metadata.favorite
+        let ocId = metadata.ocId
+
+        NextcloudKit.shared.setFavorite(fileName: fileName, favorite: favorite) { account, error in
+            if error == .success && metadata.account == account {
+                metadata.favorite = favorite
+                NCManageDatabase.shared.addMetadata(metadata)
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterFavoriteFile, userInfo: ["ocId": ocId, "serverUrl": metadata.serverUrl])
+            }
+            completion(error)
+        }
+    }
+
+    // MARK: - Lock Files
+
+    func lockUnlockFile(_ metadata: tableMetadata, shoulLock: Bool) {
+
+        NextcloudKit.shared.lockUnlockFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName, shouldLock: shoulLock) { _, error in
+            // 0: lock was successful; 412: lock did not change, no error, refresh
+            guard error == .success || error.errorCode == NCGlobal.shared.errorPreconditionFailed else {
+                let error = NKError(errorCode: error.errorCode, errorDescription: "_files_lock_error_")
+                NCContentPresenter().messageNotification(metadata.fileName, error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
+                return
+            }
+            NCNetworking.shared.readFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName) { _, metadata, error in
+                guard error == .success, let metadata = metadata else { return }
+                NCManageDatabase.shared.addMetadata(metadata)
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
+            }
+        }
+    }
+
+    // MARK: - Direct Download
+
+    func getVideoUrl(metadata: tableMetadata,
+                     completition: @escaping (_ url: URL?, _ autoplay: Bool, _ error: NKError) -> Void) {
+
+        if !metadata.url.isEmpty {
+            if metadata.url.hasPrefix("/") {
+                completition(URL(fileURLWithPath: metadata.url), true, .success)
+            } else {
+                completition(URL(string: metadata.url), true, .success)
+            }
+        } else if utilityFileSystem.fileProviderStorageExists(metadata) {
+            completition(URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)), false, .success)
+        } else {
+            NextcloudKit.shared.getDirectDownload(fileId: metadata.fileId) { _, url, _, error in
+                if error == .success && url != nil {
+                    if let url = URL(string: url!) {
+                        completition(url, false, error)
+                    } else {
+                        completition(nil, false, error)
+                    }
+                } else {
+                    completition(nil, false, error)
+                }
+            }
+        }
+    }
+
+    // MARK: - Get Preview
+
+    func getPreview(url: URL,
+                    options: NKRequestOptions = NKRequestOptions()) async -> (account: String, data: Data?, error: NKError) {
+
+        await withUnsafeContinuation({ continuation in
+            NextcloudKit.shared.getPreview(url: url, options: options) { account, data, error in
+                continuation.resume(returning: (account: account, data: data, error: error))
+            }
+        })
+    }
+
+    // MARK: - Download Preview
+
+    func downloadPreview(fileNamePathOrFileId: String,
+                         fileNamePreviewLocalPath: String,
+                         widthPreview: Int,
+                         heightPreview: Int,
+                         fileNameIconLocalPath: String? = nil,
+                         sizeIcon: Int = 0,
+                         etag: String? = nil,
+                         endpointTrashbin: Bool = false,
+                         useInternalEndpoint: Bool = true,
+                         options: NKRequestOptions = NKRequestOptions()) async -> (account: String, imagePreview: UIImage?, imageIcon: UIImage?, imageOriginal: UIImage?, etag: String?, error: NKError) {
+
+        await withUnsafeContinuation({ continuation in
+            NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId, fileNamePreviewLocalPath: fileNamePreviewLocalPath, widthPreview: widthPreview, heightPreview: heightPreview, fileNameIconLocalPath: fileNameIconLocalPath, sizeIcon: sizeIcon, etag: etag, options: options) { account, imagePreview, imageIcon, imageOriginal, etag, error in
+                continuation.resume(returning: (account: account, imagePreview: imagePreview, imageIcon: imageIcon, imageOriginal: imageOriginal, etag: etag, error: error))
+            }
+        })
+    }
+
+    // MARK: - Search
+
+    /// WebDAV search
+    func searchFiles(urlBase: NCUserBaseUrl,
+                     literal: String,
+                     completion: @escaping (_ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
+
+        NextcloudKit.shared.searchLiteral(serverUrl: urlBase.urlBase,
+                                          depth: "infinity",
+                                          literal: literal,
+                                          showHiddenFiles: NCKeychain().showHiddenFiles,
+                                          options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
+
+            guard error == .success else {
+                return completion(nil, error)
+            }
+
+            NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, metadatasFolder, metadatas in
+
+                // Update sub directories
+                for folder in metadatasFolder {
+                    let serverUrl = folder.serverUrl + "/" + folder.fileName
+                    NCManageDatabase.shared.addDirectory(encrypted: folder.e2eEncrypted, favorite: folder.favorite, ocId: folder.ocId, fileId: folder.fileId, etag: nil, permissions: folder.permissions, serverUrl: serverUrl, account: account)
+                }
+
+                NCManageDatabase.shared.addMetadatas(metadatas)
+                let metadatas = Array(metadatas.map(tableMetadata.init))
+                completion(metadatas, error)
+            }
+        }
+    }
+
+    /// Unified Search (NC>=20)
+    ///
+    func unifiedSearchFiles(userBaseUrl: NCUserBaseUrl,
+                            literal: String,
+                            providers: @escaping (_ accout: String, _ searchProviders: [NKSearchProvider]?) -> Void,
+                            update: @escaping (_ account: String, _ id: String, NKSearchResult?, [tableMetadata]?) -> Void,
+                            completion: @escaping (_ account: String, _ error: NKError) -> Void) {
+
+        let dispatchGroup = DispatchGroup()
+        dispatchGroup.enter()
+        dispatchGroup.notify(queue: .main) {
+            completion(userBaseUrl.account, NKError())
+        }
+
+        NextcloudKit.shared.unifiedSearch(term: literal, timeout: 30, timeoutProvider: 90) { _ in
+            // example filter
+            // ["calendar", "files", "fulltextsearch"].contains(provider.id)
+            return true
+        } request: { request in
+            if let request = request {
+                self.requestsUnifiedSearch.append(request)
+            }
+        } providers: { account, searchProviders in
+            providers(account, searchProviders)
+        } update: { account, partialResult, provider, _ in
+            guard let partialResult = partialResult else { return }
+            var metadatas: [tableMetadata] = []
+
+            switch provider.id {
+            case "files":
+                partialResult.entries.forEach({ entry in
+                    if let fileId = entry.fileId,
+                       let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
+                        metadatas.append(metadata)
+                    } else if let filePath = entry.filePath {
+                        let semaphore = DispatchSemaphore(value: 0)
+                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: dispatchGroup) { _, metadata, _ in
+                            metadatas.append(metadata)
+                            semaphore.signal()
+                        }
+                        semaphore.wait()
+                    } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
+                })
+            case "fulltextsearch":
+                // NOTE: FTS could also return attributes like files
+                // https://github.com/nextcloud/files_fulltextsearch/issues/143
+                partialResult.entries.forEach({ entry in
+                    let url = URLComponents(string: entry.resourceURL)
+                    guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
+                    if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(
+                              format: "account == %@ && path == %@ && fileName == %@",
+                              userBaseUrl.userAccount,
+                              "/remote.php/dav/files/" + userBaseUrl.user + dir,
+                              filename)) {
+                        metadatas.append(metadata)
+                    } else {
+                        let semaphore = DispatchSemaphore(value: 0)
+                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: dispatchGroup) { _, metadata, _ in
+                            metadatas.append(metadata)
+                            semaphore.signal()
+                        }
+                        semaphore.wait()
+                    }
+                })
+            default:
+                partialResult.entries.forEach({ entry in
+                    let metadata = NCManageDatabase.shared.createMetadata(account: userBaseUrl.account, user: userBaseUrl.user, userId: userBaseUrl.userId, fileName: entry.title, fileNameView: entry.title, ocId: NSUUID().uuidString, serverUrl: userBaseUrl.urlBase, urlBase: userBaseUrl.urlBase, url: entry.resourceURL, contentType: "", isUrl: true, name: partialResult.id, subline: entry.subline, iconName: entry.icon, iconUrl: entry.thumbnailURL)
+                    metadatas.append(metadata)
+                })
+            }
+            update(account, provider.id, partialResult, metadatas)
+        } completion: { _, _, _ in
+            self.requestsUnifiedSearch.removeAll()
+            dispatchGroup.leave()
+        }
+    }
+
+    func unifiedSearchFilesProvider(userBaseUrl: NCUserBaseUrl,
+                                    id: String, term: String,
+                                    limit: Int, cursor: Int,
+                                    completion: @escaping (_ account: String, _ searchResult: NKSearchResult?, _ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
+
+        var metadatas: [tableMetadata] = []
+
+        let request = NextcloudKit.shared.searchProvider(id, account: userBaseUrl.account, term: term, limit: limit, cursor: cursor, timeout: 60) { account, searchResult, _, error in
+            guard let searchResult = searchResult else {
+                completion(account, nil, metadatas, error)
+                return
+            }
+
+            switch id {
+            case "files":
+                searchResult.entries.forEach({ entry in
+                    if let fileId = entry.fileId, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
+                        metadatas.append(metadata)
+                    } else if let filePath = entry.filePath {
+                        let semaphore = DispatchSemaphore(value: 0)
+                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: nil) { _, metadata, _ in
+                            metadatas.append(metadata)
+                            semaphore.signal()
+                        }
+                        semaphore.wait()
+                    } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
+                })
+            case "fulltextsearch":
+                // NOTE: FTS could also return attributes like files
+                // https://github.com/nextcloud/files_fulltextsearch/issues/143
+                searchResult.entries.forEach({ entry in
+                    let url = URLComponents(string: entry.resourceURL)
+                    guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
+                    if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && path == %@ && fileName == %@", userBaseUrl.userAccount, "/remote.php/dav/files/" + userBaseUrl.user + dir, filename)) {
+                        metadatas.append(metadata)
+                    } else {
+                        let semaphore = DispatchSemaphore(value: 0)
+                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: nil) { _, metadata, _ in
+                            metadatas.append(metadata)
+                            semaphore.signal()
+                        }
+                        semaphore.wait()
+                    }
+                })
+            default:
+                searchResult.entries.forEach({ entry in
+                    let newMetadata = NCManageDatabase.shared.createMetadata(account: userBaseUrl.account, user: userBaseUrl.user, userId: userBaseUrl.userId, fileName: entry.title, fileNameView: entry.title, ocId: NSUUID().uuidString, serverUrl: userBaseUrl.urlBase, urlBase: userBaseUrl.urlBase, url: entry.resourceURL, contentType: "", isUrl: true, name: searchResult.name.lowercased(), subline: entry.subline, iconName: entry.icon, iconUrl: entry.thumbnailURL)
+                    metadatas.append(newMetadata)
+                })
+            }
+
+            completion(account, searchResult, metadatas, error)
+        }
+        if let request = request {
+            requestsUnifiedSearch.append(request)
+        }
+    }
+
+    func cancelUnifiedSearchFiles() {
+        for request in requestsUnifiedSearch {
+            request.cancel()
+        }
+        requestsUnifiedSearch.removeAll()
+    }
+
+    private func loadMetadata(userBaseUrl: NCUserBaseUrl,
+                              filePath: String,
+                              dispatchGroup: DispatchGroup? = nil,
+                              completion: @escaping (String, tableMetadata, NKError) -> Void) {
+
+        let urlPath = userBaseUrl.urlBase + "/remote.php/dav/files/" + userBaseUrl.user + filePath
+        dispatchGroup?.enter()
+        self.readFile(serverUrlFileName: urlPath) { account, metadata, error in
+            defer { dispatchGroup?.leave() }
+            guard let metadata = metadata else { return }
+            let returnMetadata = tableMetadata.init(value: metadata)
+            NCManageDatabase.shared.addMetadata(metadata)
+            completion(account, returnMetadata, error)
+        }
+    }
+
+    func cancelDataTask() {
+
+        let sessionManager = NextcloudKit.shared.sessionManager
+        sessionManager.session.getTasksWithCompletionHandler { dataTasks, _, _ in
+            dataTasks.forEach {
+                $0.cancel()
+            }
+        }
+    }
+}
+
+class NCOperationDownloadAvatar: ConcurrentOperation {
+
+    var user: String
+    var fileName: String
+    var etag: String?
+    var fileNameLocalPath: String
+    var cell: NCCellProtocol!
+    var view: UIView?
+    var cellImageView: UIImageView?
+
+    init(user: String, fileName: String, fileNameLocalPath: String, cell: NCCellProtocol, view: UIView?, cellImageView: UIImageView?) {
+        self.user = user
+        self.fileName = fileName
+        self.fileNameLocalPath = fileNameLocalPath
+        self.cell = cell
+        self.view = view
+        self.etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag
+        self.cellImageView = cellImageView
+    }
+
+    override func start() {
+
+        guard !isCancelled else { return self.finish() }
+
+        NextcloudKit.shared.downloadAvatar(user: user,
+                                           fileNameLocalPath: fileNameLocalPath,
+                                           sizeImage: NCGlobal.shared.avatarSize,
+                                           avatarSizeRounded: NCGlobal.shared.avatarSizeRounded,
+                                           etag: self.etag,
+                                           options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imageAvatar, _, etag, error in
+
+            if error == .success, let imageAvatar = imageAvatar, let etag = etag {
+                NCManageDatabase.shared.addAvatar(fileName: self.fileName, etag: etag)
+                DispatchQueue.main.async {
+                    if self.user == self.cell.fileUser, let avatarImageView = self.cellImageView {
+                        UIView.transition(with: avatarImageView,
+                                          duration: 0.75,
+                                          options: .transitionCrossDissolve,
+                                          animations: { avatarImageView.image = imageAvatar },
+                                          completion: nil)
+                    } else {
+                        if self.view is UICollectionView {
+                            (self.view as? UICollectionView)?.reloadData()
+                        } else if self.view is UITableView {
+                            (self.view as? UITableView)?.reloadData()
+                        }
+                    }
+                }
+            } else if error.errorCode == NCGlobal.shared.errorNotModified {
+                NCManageDatabase.shared.setAvatarLoaded(fileName: self.fileName)
+            }
+            self.finish()
+        }
+    }
+}

+ 20 - 1934
iOSClient/Networking/NCNetworking.swift

@@ -25,16 +25,13 @@ import UIKit
 import OpenSSL
 import NextcloudKit
 import Alamofire
-import Photos
 import Queuer
-import JGProgressHUD
-import RealmSwift
 
 @objc public protocol NCNetworkingDelegate {
     @objc optional func downloadProgress(_ progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String, session: URLSession, task: URLSessionTask)
     @objc optional func uploadProgress(_ progress: Float, totalBytes: Int64, totalBytesExpected: Int64, fileName: String, serverUrl: String, session: URLSession, task: URLSessionTask)
-    @objc optional func downloadComplete(fileName: String, serverUrl: String, etag: String?, date: NSDate?, dateLastModified: NSDate?, length: Int64, description: String?, task: URLSessionTask, error: NKError)
-    @objc optional func uploadComplete(fileName: String, serverUrl: String, ocId: String?, etag: String?, date: NSDate?, size: Int64, description: String?, task: URLSessionTask, error: NKError)
+    @objc optional func downloadComplete(fileName: String, serverUrl: String, etag: String?, date: NSDate?, dateLastModified: NSDate?, length: Int64, fileNameLocalPath: String?, task: URLSessionTask, error: NKError)
+    @objc optional func uploadComplete(fileName: String, serverUrl: String, ocId: String?, etag: String?, date: NSDate?, size: Int64, fileNameLocalPath: String?, task: URLSessionTask, error: NKError)
 }
 
 #if EXTENSION_FILE_PROVIDER_EXTENSION || EXTENSION_WIDGET
@@ -53,6 +50,12 @@ class NCNetworking: NSObject, NKCommonDelegate {
         var progress: Float
     }
 
+    struct FileNameServerUrl: Hashable {
+        var fileName: String
+        var serverUrl: String
+
+    }
+
     weak var delegate: NCNetworkingDelegate?
     let utilityFileSystem = NCUtilityFileSystem()
     let utility = NCUtility()
@@ -60,7 +63,8 @@ class NCNetworking: NSObject, NKCommonDelegate {
     var networkReachability: NKCommon.TypeReachability?
     let downloadRequest = ThreadSafeDictionary<String, DownloadRequest>()
     let uploadRequest = ThreadSafeDictionary<String, UploadRequest>()
-    let uploadMetadataInBackground = ThreadSafeDictionary<String, tableMetadata>()
+    let uploadMetadataInBackground = ThreadSafeDictionary<FileNameServerUrl, tableMetadata>()
+    let downloadMetadataInBackground = ThreadSafeDictionary<FileNameServerUrl, tableMetadata>()
     var transferInForegorund: TransferInForegorund?
 
     lazy var nkBackground: NKBackground = {
@@ -176,30 +180,6 @@ class NCNetworking: NSObject, NKCommonDelegate {
         }
     }
 
-    func downloadProgress(_ progress: Float,
-                          totalBytes: Int64,
-                          totalBytesExpected: Int64,
-                          fileName: String,
-                          serverUrl: String,
-                          session: URLSession,
-                          task: URLSessionTask) {
-
-        delegate?.downloadProgress?(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
-    }
-
-    func downloadComplete(fileName: String,
-                          serverUrl: String,
-                          etag: String?,
-                          date: NSDate?,
-                          dateLastModified: NSDate?,
-                          length: Int64,
-                          description: String?,
-                          task: URLSessionTask,
-                          error: NKError) {
-
-        delegate?.downloadComplete?(fileName: fileName, serverUrl: serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, description: description, task: task, error: error)
-    }
-
     func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
 
 #if !EXTENSION
@@ -211,10 +191,9 @@ class NCNetworking: NSObject, NKCommonDelegate {
 #endif
     }
 
-    // MARK: - Queue
+    // MARK: -
 
     func cancelAllQueue() {
-
         downloadQueue.cancelAll()
         downloadThumbnailQueue.cancelAll()
         downloadThumbnailActivityQueue.cancelAll()
@@ -224,6 +203,15 @@ class NCNetworking: NSObject, NKCommonDelegate {
         convertLivePhotoQueue.cancelAll()
     }
 
+    func cancelAllTask() {
+        cancelAllQueue()
+        cancelDataTask()
+        cancelDownloadTasks()
+        cancelUploadTasks()
+        cancelDownloadBackgroundTask()
+        cancelUploadBackgroundTask()
+    }
+
     // MARK: - Pinning check
 
     public func checkTrustedChallenge(_ session: URLSession,
@@ -344,1906 +332,4 @@ class NCNetworking: NSObject, NKCommonDelegate {
             }
         }
     }
-
-    // MARK: - Download
-
-    func download(metadata: tableMetadata,
-                  withNotificationProgressTask: Bool,
-                  checkfileProviderStorageExists: Bool = false,
-                  hudView: UIView? = nil,
-                  hud: JGProgressHUD? = nil,
-                  start: @escaping () -> Void = { },
-                  requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
-                  progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
-                  completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
-
-        if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload {
-            downloadFile(metadata: metadata, withNotificationProgressTask: withNotificationProgressTask, checkfileProviderStorageExists: checkfileProviderStorageExists, hudView: hudView, hud: hud) {
-                start()
-            } requestHandler: { request in
-                requestHandler(request)
-            } progressHandler: { progress in
-                progressHandler(progress)
-            } completion: { afError, error in
-                completion(afError, error)
-
-            }
-        } else {
-            /*
-            uploadFileInBackground(metadata: metadata, start: start) { error in
-                completion(error)
-            }
-            */
-        }
-    }
-
-    private func downloadFile(metadata: tableMetadata,
-                              withNotificationProgressTask: Bool,
-                              checkfileProviderStorageExists: Bool = false,
-                              hudView: UIView?,
-                              hud: JGProgressHUD?,
-                              start: @escaping () -> Void = { },
-                              requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
-                              progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
-                              completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
-
-        guard !metadata.isInTransfer else { return completion(nil, NKError()) }
-        if checkfileProviderStorageExists, utilityFileSystem.fileProviderStorageExists(metadata) {
-            return completion(nil, NKError())
-        }
-
-        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
-        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
-        let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
-
-        if NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) == nil {
-            NCManageDatabase.shared.addMetadata(metadata)
-        }
-
-        NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, options: options, requestHandler: { request in
-
-            self.downloadRequest[fileNameLocalPath] = request
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                       status: NCGlobal.shared.metadataStatusDownloading)
-            requestHandler(request)
-
-        }, taskHandler: { task in
-
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                       taskIdentifier: task.taskIdentifier)
-
-            NotificationCenter.default.post(
-                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
-                object: nil,
-                userInfo: ["ocId": metadata.ocId,
-                           "serverUrl": metadata.serverUrl,
-                           "account": metadata.account])
-
-            start()
-
-        }, progressHandler: { progress in
-
-            if withNotificationProgressTask, Int(floor(progress.fractionCompleted * 100)).isMultiple(of: 5) {
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
-                    object: nil,
-                    userInfo: ["account": metadata.account,
-                               "ocId": metadata.ocId,
-                               "fileName": metadata.fileName,
-                               "serverUrl": metadata.serverUrl,
-                               "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
-                               "progress": NSNumber(value: progress.fractionCompleted),
-                               "totalBytes": NSNumber(value: progress.totalUnitCount),
-                               "totalBytesExpected": NSNumber(value: progress.completedUnitCount)])
-            }
-            progressHandler(progress)
-
-        }) { _, etag, _, _, _, afError, error in
-
-            self.downloadRequest.removeValue(forKey: fileNameLocalPath)
-
-            var sessionTaskFailedCode = 0
-            if let error = NextcloudKit.shared.nkCommonInstance.getSessionErrorFromAFError(afError) {
-                sessionTaskFailedCode = error.code
-            }
-
-            if afError?.isExplicitlyCancelledError ?? false || sessionTaskFailedCode == NCGlobal.shared.errorExplicitlyCancelled {
-
-                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                           session: "",
-                                                           sessionError: "",
-                                                           selector: "",
-                                                           status: NCGlobal.shared.metadataStatusNormal,
-                                                           errorCode: 0)
-                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
-                                                object: nil,
-                                                userInfo: ["ocId": metadata.ocId,
-                                                           "serverUrl": metadata.serverUrl,
-                                                           "account": metadata.account])
-
-            } else if error == .success {
-
-#if !EXTENSION
-                if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
-                    NCEndToEndEncryption.sharedManager()?.decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
-                }
-#endif
-                NCManageDatabase.shared.addLocalFile(metadata: metadata)
-                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                           session: "",
-                                                           sessionError: "",
-                                                           status: NCGlobal.shared.metadataStatusNormal,
-                                                           etag: etag,
-                                                           errorCode: 0)
-                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
-                                                object: nil,
-                                                userInfo: ["ocId": metadata.ocId,
-                                                           "serverUrl": metadata.serverUrl,
-                                                           "account": metadata.account,
-                                                           "selector": metadata.sessionSelector,
-                                                           "error": error])
-
-            } else {
-
-                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                           session: "",
-                                                           sessionError: "",
-                                                           selector: "",
-                                                           status: NCGlobal.shared.metadataStatusNormal,
-                                                           errorCode: 0)
-                NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
-                                                object: nil,
-                                                userInfo: ["ocId": metadata.ocId,
-                                                           "serverUrl": metadata.serverUrl,
-                                                           "account": metadata.account,
-                                                           "selector": metadata.sessionSelector,
-                                                           "error": error])
-            }
-
-            DispatchQueue.main.async { completion(afError, error) }
-        }
-    }
-
-#if !EXTENSION
-    func downloadAvatar(user: String,
-                        dispalyName: String?,
-                        fileName: String,
-                        cell: NCCellProtocol,
-                        view: UIView?,
-                        cellImageView: UIImageView?) {
-
-        let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
-
-        if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
-            cellImageView?.image = image
-            cell.fileAvatarImageView?.image = image
-            return
-        }
-
-        if let account = NCManageDatabase.shared.getActiveAccount() {
-            cellImageView?.image = utility.loadUserImage(for: user, displayName: dispalyName, userBaseUrl: account)
-        }
-
-        for case let operation as NCOperationDownloadAvatar in downloadAvatarQueue.operations where operation.fileName == fileName { return }
-        downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view, cellImageView: cellImageView))
-    }
-#endif
-
-    // MARK: - Upload
-
-    func upload(metadata: tableMetadata,
-                uploadE2EEDelegate: uploadE2EEDelegate? = nil,
-                hudView: UIView?,
-                hud: JGProgressHUD?,
-                start: @escaping () -> Void = { },
-                requestHandler: @escaping (_ request: UploadRequest) -> Void = { _ in },
-                progressHandler: @escaping (_ totalBytesExpected: Int64, _ totalBytes: Int64, _ fractionCompleted: Double) -> Void = { _, _, _ in },
-                completion: @escaping (_ error: NKError) -> Void = { _ in }) {
-
-        let metadata = tableMetadata.init(value: metadata)
-        var numChunks: Int = 0
-        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Upload file \(metadata.fileNameView) with Identifier \(metadata.assetLocalIdentifier) with size \(metadata.size) [CHUNK \(metadata.chunk), E2EE \(metadata.isDirectoryE2EE)]")
-
-        if metadata.isDirectoryE2EE {
-#if !EXTENSION_FILE_PROVIDER_EXTENSION && !EXTENSION_WIDGET
-            Task {
-                let error = await NCNetworkingE2EEUpload().upload(metadata: metadata, uploadE2EEDelegate: uploadE2EEDelegate, hudView: hudView, hud: hud)
-                completion(error)
-            }
-#endif
-        } else if metadata.chunk > 0 {
-                if let hudView {
-                    DispatchQueue.main.async {
-                        if let hud {
-                            hud.indicatorView = JGProgressHUDRingIndicatorView()
-                            if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
-                                indicatorView.ringWidth = 1.5
-                            }
-                            hud.tapOnHUDViewBlock = { _ in
-                                NotificationCenter.default.postOnMainThread(name: "NextcloudKit.chunkedFile.stop")
-                            }
-                            hud.textLabel.text = NSLocalizedString("_wait_file_preparation_", comment: "")
-                            hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
-                            hud.show(in: hudView)
-                        }
-                    }
-                }
-            uploadChunkFile(metadata: metadata) { num in
-                numChunks = num
-            } counterChunk: { counter in
-                DispatchQueue.main.async { hud?.progress = Float(counter) / Float(numChunks) }
-            } start: {
-                DispatchQueue.main.async { hud?.dismiss() }
-            } completion: { account, _, afError, error in
-                DispatchQueue.main.async { hud?.dismiss() }
-                var sessionTaskFailedCode = 0
-                let directory = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)
-                if let error = NextcloudKit.shared.nkCommonInstance.getSessionErrorFromAFError(afError) {
-                    sessionTaskFailedCode = error.code
-                }
-                switch error.errorCode {
-                case NKError.chunkNoEnoughMemory, NKError.chunkCreateFolder, NKError.chunkFilesNull, NKError.chunkFileNull:
-                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                    NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
-                    NCContentPresenter().messageNotification("_error_files_upload_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error, afterDelay: 0.5)
-                case NKError.chunkFileUpload:
-                    if let afError, (afError.isExplicitlyCancelledError || sessionTaskFailedCode == NCGlobal.shared.errorExplicitlyCancelled ) {
-                        NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
-                    }
-                case NKError.chunkMoveFile:
-                    NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
-                    NCContentPresenter().messageNotification("_chunk_move_", error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error, afterDelay: 0.5)
-                default: break
-                }
-                completion(error)
-            }
-        } else if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload {
-            let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
-            uploadFile(metadata: metadata, fileNameLocalPath: fileNameLocalPath, start: start, progressHandler: progressHandler) { _, _, _, _, _, _, _, error in
-                completion(error)
-            }
-        } else {
-            uploadFileInBackground(metadata: metadata, start: start) { error in
-                completion(error)
-            }
-        }
-    }
-
-    func uploadFile(metadata: tableMetadata,
-                    fileNameLocalPath: String,
-                    withUploadComplete: Bool = true,
-                    customHeaders: [String: String]? = nil,
-                    start: @escaping () -> Void = { },
-                    requestHandler: @escaping (_ request: UploadRequest) -> Void = { _ in },
-                    progressHandler: @escaping (_ totalBytesExpected: Int64, _ totalBytes: Int64, _ fractionCompleted: Double) -> Void = { _, _, _ in },
-                    completion: @escaping (_ account: String, _ ocId: String?, _ etag: String?, _ date: NSDate?, _ size: Int64, _ allHeaderFields: [AnyHashable: Any]?, _ afError: AFError?, _ error: NKError) -> Void) {
-
-        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
-        var uploadTask: URLSessionTask?
-        let description = metadata.ocId
-        let options = NKRequestOptions(customHeader: customHeaders, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
-
-        NextcloudKit.shared.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: metadata.creationDate as Date, dateModificationFile: metadata.date as Date, options: options, requestHandler: { request in
-
-            self.uploadRequest[fileNameLocalPath] = request
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                       status: NCGlobal.shared.metadataStatusUploading)
-            requestHandler(request)
-
-        }, taskHandler: { task in
-
-            uploadTask = task
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                       taskIdentifier: task.taskIdentifier)
-
-            NotificationCenter.default.post(
-                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile),
-                object: nil,
-                userInfo: ["ocId": metadata.ocId,
-                           "serverUrl": metadata.serverUrl,
-                           "account": metadata.account,
-                           "fileName": metadata.fileName,
-                           "sessionSelector": metadata.sessionSelector])
-            start()
-
-        }, progressHandler: { progress in
-
-            if Int(floor(progress.fractionCompleted * 100)).isMultiple(of: 5) {
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
-                    object: nil,
-                    userInfo: [
-                        "account": metadata.account,
-                        "ocId": metadata.ocId,
-                        "fileName": metadata.fileName,
-                        "serverUrl": metadata.serverUrl,
-                        "status": NSNumber(value: NCGlobal.shared.metadataStatusUploading),
-                        "progress": NSNumber(value: progress.fractionCompleted),
-                        "totalBytes": NSNumber(value: progress.totalUnitCount),
-                        "totalBytesExpected": NSNumber(value: progress.completedUnitCount)])
-            }
-
-            progressHandler(progress.completedUnitCount, progress.totalUnitCount, progress.fractionCompleted)
-
-        }) { account, ocId, etag, date, size, allHeaderFields, afError, error in
-
-            self.uploadRequest.removeValue(forKey: fileNameLocalPath)
-            if withUploadComplete, let uploadTask = uploadTask {
-                self.uploadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, ocId: ocId, etag: etag, date: date, size: size, description: description, task: uploadTask, error: error)
-            }
-            completion(account, ocId, etag, date, size, allHeaderFields, afError, error)
-        }
-    }
-
-    func uploadChunkFile(metadata: tableMetadata,
-                         withUploadComplete: Bool = true,
-                         customHeaders: [String: String]? = nil,
-                         numChunks: @escaping (_ num: Int) -> Void = { _ in },
-                         counterChunk: @escaping (_ counter: Int) -> Void = { _ in },
-                         start: @escaping () -> Void = { },
-                         progressHandler: @escaping (_ totalBytesExpected: Int64, _ totalBytes: Int64, _ fractionCompleted: Double) -> Void = { _, _, _ in },
-                         completion: @escaping (_ account: String, _ file: NKFile?, _ afError: AFError?, _ error: NKError) -> Void) {
-
-        let directory = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId)
-        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
-        let chunkFolder = NCManageDatabase.shared.getChunkFolder(account: metadata.account, ocId: metadata.ocId)
-        let filesChunk = NCManageDatabase.shared.getChunks(account: metadata.account, ocId: metadata.ocId)
-        var uploadTask: URLSessionTask?
-
-        var chunkSize = NCGlobal.shared.chunkSizeMBCellular
-        if NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi {
-            chunkSize = NCGlobal.shared.chunkSizeMBEthernetOrWiFi
-        }
-        let options = NKRequestOptions(customHeader: customHeaders, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
-
-        NextcloudKit.shared.uploadChunk(directory: directory, fileName: metadata.fileName, date: metadata.date as Date, creationDate: metadata.creationDate as Date, serverUrl: metadata.serverUrl, chunkFolder: chunkFolder, filesChunk: filesChunk, chunkSize: chunkSize, options: options) { num in
-
-            numChunks(num)
-
-        } counterChunk: { counter in
-
-            counterChunk(counter)
-
-        } start: { filesChunk in
-
-            start()
-            NCManageDatabase.shared.addChunks(account: metadata.account, ocId: metadata.ocId, chunkFolder: chunkFolder, filesChunk: filesChunk)
-            NotificationCenter.default.post(
-                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile),
-                object: nil,
-                userInfo: ["ocId": metadata.ocId,
-                           "serverUrl": metadata.serverUrl,
-                           "account": metadata.account,
-                           "fileName": metadata.fileName,
-                           "sessionSelector": metadata.sessionSelector])
-
-        } requestHandler: { request in
-
-            self.uploadRequest[fileNameLocalPath] = request
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                       status: NCGlobal.shared.metadataStatusUploading)
-
-        } taskHandler: { task in
-
-            uploadTask = task
-            NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                       taskIdentifier: task.taskIdentifier)
-
-        } progressHandler: { totalBytesExpected, totalBytes, fractionCompleted in
-
-            NotificationCenter.default.post(
-                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
-                object: nil,
-                userInfo: [
-                    "account": metadata.account,
-                    "ocId": metadata.ocId,
-                    "fileName": metadata.fileName,
-                    "serverUrl": metadata.serverUrl,
-                    "status": NSNumber(value: NCGlobal.shared.metadataStatusUploading),
-                    "chunk": metadata.chunk,
-                    "e2eEncrypted": metadata.e2eEncrypted,
-                    "progress": NSNumber(value: fractionCompleted),
-                    "totalBytes": NSNumber(value: totalBytes),
-                    "totalBytesExpected": NSNumber(value: totalBytesExpected)])
-
-            progressHandler(totalBytesExpected, totalBytes, fractionCompleted)
-
-        } uploaded: { fileChunk in
-
-            NCManageDatabase.shared.deleteChunk(account: metadata.account, ocId: metadata.ocId, fileChunk: fileChunk, directory: directory)
-
-        } completion: { account, _, file, afError, error in
-
-            self.uploadRequest.removeValue(forKey: fileNameLocalPath)
-            if error == .success {
-                NCManageDatabase.shared.deleteChunks(account: account, ocId: metadata.ocId, directory: directory)
-            }
-            if withUploadComplete, let uploadTask {
-                self.uploadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, ocId: file?.ocId, etag: file?.etag, date: file?.date, size: file?.size ?? 0, description: metadata.ocId, task: uploadTask, error: error)
-            }
-            completion(account, file, afError, error)
-        }
-    }
-
-    private func uploadFileInBackground(metadata: tableMetadata,
-                                        start: @escaping () -> Void = { },
-                                        completion: @escaping (_ error: NKError) -> Void) {
-
-        var session: URLSession?
-        let metadata = tableMetadata.init(value: metadata)
-        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
-        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
-
-        if metadata.session == sessionUploadBackground || metadata.session == sessionUploadBackgroundExtension {
-            session = sessionManagerUploadBackground
-        } else if metadata.session == sessionUploadBackgroundWWan {
-            session = sessionManagerUploadBackgroundWWan
-        }
-
-        start()
-
-        // Check file dim > 0
-        if utilityFileSystem.getFileSize(filePath: fileNameLocalPath) == 0 && metadata.size != 0 {
-
-            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-            completion(NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: NSLocalizedString("_error_not_found_", value: "The requested resource could not be found", comment: "")))
-
-        } else {
-
-            if let task = nkBackground.upload(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, dateCreationFile: metadata.creationDate as Date, dateModificationFile: metadata.date as Date, description: metadata.ocId, session: session!) {
-
-                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Upload file \(metadata.fileNameView) with task with taskIdentifier \(task.taskIdentifier)")
-
-                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                           status: NCGlobal.shared.metadataStatusUploading,
-                                                           taskIdentifier: task.taskIdentifier)
-
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadStartFile),
-                    object: nil,
-                    userInfo: ["ocId": metadata.ocId,
-                               "serverUrl": metadata.serverUrl,
-                               "account": metadata.account,
-                               "fileName": metadata.fileName,
-                               "sessionSelector": metadata.sessionSelector])
-                completion(NKError())
-
-            } else {
-
-                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                completion(NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: "task null"))
-            }
-        }
-    }
-
-    func uploadComplete(fileName: String,
-                        serverUrl: String,
-                        ocId: String?,
-                        etag: String?,
-                        date: NSDate?,
-                        size: Int64,
-                        description: String?,
-                        task: URLSessionTask,
-                        error: NKError) {
-
-        guard self.delegate == nil, let metadata = NCManageDatabase.shared.getMetadataFromOcId(description) else {
-            self.delegate?.uploadComplete?(fileName: fileName, serverUrl: serverUrl, ocId: ocId, etag: etag, date: date, size: size, description: description, task: task, error: error)
-            return
-        }
-        let ocIdTemp = metadata.ocId
-        let selector = metadata.sessionSelector
-        var isApplicationStateActive = false
-#if !EXTENSION
-        isApplicationStateActive = UIApplication.shared.applicationState == .active
-#endif
-
-        if error == .success, let ocId = ocId, size == metadata.size {
-
-            let metadata = tableMetadata.init(value: metadata)
-
-            metadata.uploadDate = date ?? NSDate()
-            metadata.etag = etag ?? ""
-            metadata.ocId = ocId
-            metadata.chunk = 0
-
-            if let fileId = utility.ocIdToFileId(ocId: ocId) {
-                metadata.fileId = fileId
-            }
-
-            metadata.session = ""
-            metadata.sessionError = ""
-            metadata.status = NCGlobal.shared.metadataStatusNormal
-
-            // Delete Asset on Photos album
-            if NCKeychain().removePhotoCameraRoll, !metadata.assetLocalIdentifier.isEmpty {
-                metadata.deleteAssetLocalIdentifier = true
-            }
-
-            NCManageDatabase.shared.addMetadata(metadata)
-            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", ocIdTemp))
-
-            if selector == NCGlobal.shared.selectorUploadFileNODelete {
-                utilityFileSystem.moveFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp), toPath: utilityFileSystem.getDirectoryProviderStorageOcId(ocId))
-                NCManageDatabase.shared.addLocalFile(metadata: metadata)
-            } else {
-                utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(ocIdTemp))
-            }
-
-            NextcloudKit.shared.nkCommonInstance.writeLog("[SUCCESS] Upload complete " + serverUrl + "/" + fileName + ", result: success(\(size) bytes)")
-
-            let userInfo: [AnyHashable: Any] = ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "fileName": metadata.fileName, "ocIdTemp": ocIdTemp, "error": error]
-            if metadata.isLivePhoto, NCGlobal.shared.isLivePhotoServerAvailable {
-                uploadLivePhoto(metadata: metadata, userInfo: userInfo)
-            } else {
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
-                    object: nil,
-                    userInfo: userInfo)
-            }
-        } else {
-
-            if error.errorCode == NSURLErrorCancelled || error.errorCode == NCGlobal.shared.errorRequestExplicityCancelled {
-
-                utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
-                    object: nil,
-                    userInfo: ["ocId": metadata.ocId,
-                               "serverUrl": metadata.serverUrl,
-                               "account": metadata.account])
-
-            } else if error.errorCode == NCGlobal.shared.errorBadRequest || error.errorCode == NCGlobal.shared.errorUnsupportedMediaType {
-
-                utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
-                    object: nil,
-                    userInfo: ["ocId": metadata.ocId,
-                               "serverUrl": metadata.serverUrl,
-                               "account": metadata.account])
-                if isApplicationStateActive {
-                    NCContentPresenter().showError(error: NKError(errorCode: error.errorCode, errorDescription: "_virus_detect_"))
-                }
-
-                // Client Diagnostic
-                NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueVirusDetected)
-
-            } else if error.errorCode == NCGlobal.shared.errorForbidden && isApplicationStateActive {
-#if !EXTENSION
-                DispatchQueue.main.async {
-                    let newFileName = self.utilityFileSystem.createFileName(metadata.fileName, serverUrl: metadata.serverUrl, account: metadata.account)
-                    let alertController = UIAlertController(title: error.errorDescription, message: NSLocalizedString("_change_upload_filename_", comment: ""), preferredStyle: .alert)
-                    alertController.addAction(UIAlertAction(title: String(format: NSLocalizedString("_save_file_as_", comment: ""), newFileName), style: .default, handler: { _ in
-                        let atpath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName
-                        let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + newFileName
-                        self.utilityFileSystem.moveFile(atPath: atpath, toPath: toPath)
-                        NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                                   newFileName: newFileName,
-                                                                   sessionError: "",
-                                                                   status: NCGlobal.shared.metadataStatusWaitUpload,
-                                                                   errorCode: error.errorCode)
-                    }))
-                    alertController.addAction(UIAlertAction(title: NSLocalizedString("_discard_changes_", comment: ""), style: .destructive, handler: { _ in
-                        self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-                        NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                        NotificationCenter.default.post(
-                            name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
-                            object: nil,
-                            userInfo: ["ocId": metadata.ocId,
-                                       "serverUrl": metadata.serverUrl,
-                                       "account": metadata.account])
-                    }))
-
-                    let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
-                    appDelegate.window?.rootViewController?.present(alertController, animated: true)
-
-                    // Client Diagnostic
-                    NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueProblems, error: NCGlobal.shared.diagnosticProblemsForbidden)
-                }
-#endif
-            } else {
-
-                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                           sessionError: error.errorDescription,
-                                                           status: NCGlobal.shared.metadataStatusUploadError,
-                                                           errorCode: error.errorCode)
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile),
-                    object: nil,
-                    userInfo: ["ocId": metadata.ocId,
-                               "serverUrl": metadata.serverUrl,
-                               "account": metadata.account,
-                               "fileName": metadata.fileName,
-                               "ocIdTemp": ocIdTemp,
-                               "error": error])
-
-                // Client Diagnostic
-                if error.errorCode == NCGlobal.shared.errorInternalServerError {
-                    NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueProblems, error: NCGlobal.shared.diagnosticProblemsBadResponse)
-                } else {
-                    NCManageDatabase.shared.addDiagnostic(account: metadata.account, issue: NCGlobal.shared.diagnosticIssueProblems, error: NCGlobal.shared.diagnosticProblemsUploadServerError)
-                }
-            }
-        }
-
-        self.uploadMetadataInBackground.removeValue(forKey: fileName + serverUrl)
-        self.delegate?.uploadComplete?(fileName: fileName, serverUrl: serverUrl, ocId: ocId, etag: etag, date: date, size: size, description: description, task: task, error: error)
-    }
-
-    func uploadProgress(_ progress: Float,
-                        totalBytes: Int64,
-                        totalBytesExpected: Int64,
-                        fileName: String,
-                        serverUrl: String,
-                        session: URLSession,
-                        task: URLSessionTask) {
-
-        DispatchQueue.global().async {
-            self.delegate?.uploadProgress?(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
-
-            var metadata: tableMetadata?
-            let description: String = task.taskDescription ?? ""
-
-            if let metadataTmp = self.uploadMetadataInBackground[fileName + serverUrl] {
-                metadata = metadataTmp
-            } else if let metadataTmp = NCManageDatabase.shared.getMetadataFromOcId(description) {
-                self.uploadMetadataInBackground[fileName + serverUrl] = metadataTmp
-                metadata = metadataTmp
-            }
-
-            if let metadata = metadata, Int(floor(progress * 100)).isMultiple(of: 5) {
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
-                    object: nil,
-                    userInfo: [
-                        "account": metadata.account,
-                        "ocId": metadata.ocId,
-                        "fileName": metadata.fileName,
-                        "serverUrl": serverUrl,
-                        "status": NSNumber(value: NCGlobal.shared.metadataStatusUploading),
-                        "chunk": metadata.chunk,
-                        "e2eEncrypted": metadata.e2eEncrypted,
-                        "progress": NSNumber(value: progress),
-                        "totalBytes": NSNumber(value: totalBytes),
-                        "totalBytesExpected": NSNumber(value: totalBytesExpected)])
-            }
-        }
-    }
-
-    func getOcIdInBackgroundSession(queue: DispatchQueue = .main,
-                                    completion: @escaping (_ listOcId: [String]) -> Void) {
-
-        var listOcId: [String] = []
-
-        sessionManagerUploadBackground.getAllTasks(completionHandler: { tasks in
-            for task in tasks {
-                listOcId.append(task.description)
-            }
-            self.sessionManagerUploadBackgroundWWan.getAllTasks(completionHandler: { tasks in
-                for task in tasks {
-                    listOcId.append(task.description)
-                }
-                queue.async { completion(listOcId) }
-            })
-        })
-    }
-
-    // MARK: - Live Photo
-
-    func uploadLivePhoto(metadata: tableMetadata, userInfo aUserInfo: [AnyHashable: Any]) {
-
-        guard let metadata1 = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND urlBase == %@ AND path == %@ AND fileName == %@", metadata.account, metadata.urlBase, metadata.path, metadata.livePhotoFile)) else {
-            metadata.livePhotoFile = ""
-            NCManageDatabase.shared.addMetadata(metadata)
-            return NotificationCenter.default.post(
-                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedLivePhoto),
-                object: nil,
-                userInfo: aUserInfo)
-        }
-        if metadata1.status != NCGlobal.shared.metadataStatusNormal { return }
-
-        Task {
-            let serverUrlfileNamePath = metadata.urlBase + metadata.path + metadata.fileName
-            var livePhotoFile = metadata1.fileId
-            let results = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: livePhotoFile)
-            if results.error == .success {
-                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata.account, ocId: metadata.ocId, livePhotoFile: livePhotoFile)
-            } else {
-                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Uplod set LivePhoto with error \(results.error.errorCode)")
-            }
-
-            let serverUrlfileNamePath1 = metadata1.urlBase + metadata1.path + metadata1.fileName
-            livePhotoFile = metadata.fileId
-            let results1 = await NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath1, livePhotoFile: livePhotoFile)
-            if results1.error == .success {
-                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: metadata1.account, ocId: metadata1.ocId, livePhotoFile: livePhotoFile)
-            } else {
-                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Upload set LivePhoto with error \(results.error.errorCode)")
-            }
-            if results.error == .success, results1.error == .success {
-                NextcloudKit.shared.nkCommonInstance.writeLog("[SUCCESS] Upload set LivePhoto for files " + (metadata.fileName as NSString).deletingPathExtension)
-
-            }
-            NotificationCenter.default.post(
-                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedLivePhoto),
-                object: nil,
-                userInfo: aUserInfo)
-        }
-    }
-
-    func convertLivePhoto(metadata: tableMetadata) {
-
-        guard metadata.status == NCGlobal.shared.metadataStatusNormal else { return }
-
-        let account = metadata.account
-        let livePhotoFile = metadata.livePhotoFile
-        let serverUrlfileNamePath = metadata.urlBase + metadata.path + metadata.fileName
-        let ocId = metadata.ocId
-
-        DispatchQueue.global().async {
-            if let result = NCManageDatabase.shared.getResultMetadata(predicate: NSPredicate(format: "account == '\(account)' AND status == \(NCGlobal.shared.metadataStatusNormal) AND (fileName == '\(livePhotoFile)' || fileId == '\(livePhotoFile)')")) {
-                if livePhotoFile == result.fileId { return }
-                for case let operation as NCOperationConvertLivePhoto in self.convertLivePhotoQueue.operations where operation.serverUrlfileNamePath == serverUrlfileNamePath { continue }
-                self.convertLivePhotoQueue.addOperation(NCOperationConvertLivePhoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: result.fileId, account: account, ocId: ocId))
-            }
-        }
-    }
-
-    // MARK: - Cancel (Download Upload)
-
-    // sessionIdentifierDownload: String = "com.nextcloud.nextcloudkit.session.download"
-    // sessionIdentifierUpload: String = "com.nextcloud.nextcloudkit.session.upload"
-
-    // sessionUploadBackground: String = "com.nextcloud.session.upload.background"
-    // sessionUploadBackgroundWWan: String = "com.nextcloud.session.upload.backgroundWWan"
-    // sessionUploadBackgroundExtension: String = "com.nextcloud.session.upload.extension"
-
-    func cancelDataTask() {
-
-        let sessionManager = NextcloudKit.shared.sessionManager
-        sessionManager.session.getTasksWithCompletionHandler { dataTasks, _, _ in
-            dataTasks.forEach {
-                $0.cancel()
-            }
-        }
-    }
-
-    func cancelDownloadTasks() {
-
-        let sessionManager = NextcloudKit.shared.sessionManager
-        sessionManager.session.getTasksWithCompletionHandler { _, _, downloadTasks in
-            downloadTasks.forEach {
-                $0.cancel()
-            }
-        }
-
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0")) {
-                for metadata in results {
-                    self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-                    NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                               session: "",
-                                                               sessionError: "",
-                                                               selector: "",
-                                                               status: NCGlobal.shared.metadataStatusNormal,
-                                                               errorCode: 0)
-                }
-            }
-            self.downloadRequest.removeAll()
-        }
-    }
-
-    func cancelUploadTasks() {
-
-        uploadRequest.removeAll()
-        let sessionManager = NextcloudKit.shared.sessionManager
-        sessionManager.session.getTasksWithCompletionHandler { _, uploadTasks, _ in
-            uploadTasks.forEach {
-                $0.cancel()
-            }
-        }
-
-        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status > 0 AND session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload)) {
-                NCManageDatabase.shared.deleteMetadata(results: results)
-            }
-        }
-    }
-
-    func cancelUploadBackgroundTask(withNotification: Bool) {
-
-        Task {
-            let tasksBackground = await NCNetworking.shared.sessionManagerUploadBackground.tasks
-            for task in tasksBackground.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
-                task.cancel()
-            }
-            let tasksBackgroundWWan = await NCNetworking.shared.sessionManagerUploadBackgroundWWan.tasks
-            for task in tasksBackgroundWWan.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
-                task.cancel()
-            }
-
-            if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status > 0 AND (session == %@ || session == %@)", NCNetworking.shared.sessionUploadBackground, NCNetworking.shared.sessionUploadBackgroundWWan)) {
-                NCManageDatabase.shared.deleteMetadata(results: results)
-            }
-            if withNotification {
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
-            }
-        }
-    }
-
-    func cancel(metadata: tableMetadata) async {
-
-        let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
-        utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-
-        // No session found
-        if metadata.session.isEmpty {
-            uploadRequest.removeValue(forKey: fileNameLocalPath)
-            downloadRequest.removeValue(forKey: fileNameLocalPath)
-            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
-            return
-        }
-
-        // DOWNLOAD
-        if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload {
-            if let request = downloadRequest[fileNameLocalPath] {
-                request.cancel()
-            } else if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) {
-                NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
-                                                           session: "",
-                                                           sessionError: "",
-                                                           selector: "",
-                                                           status: NCGlobal.shared.metadataStatusNormal,
-                                                           errorCode: 0)
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
-                    object: nil,
-                    userInfo: ["ocId": metadata.ocId,
-                               "serverUrl": metadata.serverUrl,
-                               "account": metadata.account])
-            }
-            return
-        }
-
-        // UPLOAD FOREGROUND
-        if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload {
-            if let request = uploadRequest[fileNameLocalPath] {
-                request.cancel()
-                uploadRequest.removeValue(forKey: fileNameLocalPath)
-            }
-            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-            NotificationCenter.default.post(
-                name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
-                object: nil,
-                userInfo: ["ocId": metadata.ocId,
-                           "serverUrl": metadata.serverUrl,
-                           "account": metadata.account])
-            return
-        }
-
-        // UPLOAD BACKGROUND
-        var session: URLSession?
-        if metadata.session == NCNetworking.shared.sessionUploadBackground {
-            session = NCNetworking.shared.sessionManagerUploadBackground
-        } else if metadata.session == NCNetworking.shared.sessionUploadBackgroundWWan {
-            session = NCNetworking.shared.sessionManagerUploadBackgroundWWan
-        }
-        if let tasks = await session?.tasks {
-            for task in tasks.1 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
-                if task.taskIdentifier == metadata.sessionTaskIdentifier {
-                    task.cancel()
-                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                    NotificationCenter.default.post(
-                        name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUploadCancelFile),
-                        object: nil,
-                        userInfo: ["ocId": metadata.ocId,
-                                   "serverUrl": metadata.serverUrl,
-                                   "account": metadata.account])
-                }
-            }
-        }
-    }
-
-    // MARK: - WebDav Read file, folder
-
-    func readFolder(serverUrl: String,
-                    account: String,
-                    forceReplaceMetadatas: Bool = false,
-                    completion: @escaping (_ account: String, _ metadataFolder: tableMetadata?, _ metadatas: [tableMetadata]?, _ metadatasChangedCount: Int, _ metadatasChanged: Bool, _ error: NKError) -> Void) {
-
-        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl,
-                                             depth: "1",
-                                             showHiddenFiles: NCKeychain().showHiddenFiles,
-                                             includeHiddenFiles: NCGlobal.shared.includeHiddenFiles,
-                                             options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
-
-            guard error == .success else {
-                return completion(account, nil, nil, 0, false, error)
-            }
-
-            NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: true) { metadataFolder, metadatasFolder, metadatas in
-
-                // Add metadata folder
-                NCManageDatabase.shared.addMetadata(tableMetadata.init(value: metadataFolder))
-
-                // Update directory
-                NCManageDatabase.shared.addDirectory(encrypted: metadataFolder.e2eEncrypted, favorite: metadataFolder.favorite, ocId: metadataFolder.ocId, fileId: metadataFolder.fileId, etag: metadataFolder.etag, permissions: metadataFolder.permissions, serverUrl: serverUrl, account: metadataFolder.account)
-                NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, richWorkspace: metadataFolder.richWorkspace, account: metadataFolder.account)
-
-                // Update sub directories NO Update richWorkspace
-                for metadata in metadatasFolder {
-                    let serverUrl = metadata.serverUrl + "/" + metadata.fileName
-                    NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrl, account: account)
-                }
-
-#if !EXTENSION
-                // Convert Live Photo
-                for metadata in metadatas {
-                    if NCGlobal.shared.isLivePhotoServerAvailable, metadata.isLivePhoto {
-                        NCNetworking.shared.convertLivePhoto(metadata: metadata)
-                    }
-                }
-#endif
-
-                let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)
-
-                if forceReplaceMetadatas {
-                    NCManageDatabase.shared.replaceMetadata(metadatas, predicate: predicate)
-                    completion(account, metadataFolder, metadatas, 0, true, error)
-                } else {
-                    let results = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
-                    completion(account, metadataFolder, metadatas, results.metadatasChangedCount, results.metadatasChanged, error)
-                }
-            }
-        }
-    }
-
-    func readFile(serverUrlFileName: String,
-                  showHiddenFiles: Bool = NCKeychain().showHiddenFiles,
-                  queue: DispatchQueue = NextcloudKit.shared.nkCommonInstance.backgroundQueue,
-                  completion: @escaping (_ account: String, _ metadata: tableMetadata?, _ error: NKError) -> Void) {
-
-        let options = NKRequestOptions(queue: queue)
-
-        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName, depth: "0", showHiddenFiles: showHiddenFiles, options: options) { account, files, _, error in
-            guard error == .success, files.count == 1, let file = files.first else {
-                completion(account, nil, error)
-                return
-            }
-
-            let isDirectoryE2EE = self.utilityFileSystem.isDirectoryE2EE(file: file)
-            let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
-
-            completion(account, metadata, error)
-        }
-    }
-
-    func fileExists(serverUrlFileName: String,
-                    completion: @escaping (_ account: String, _ exists: Bool?, _ file: NKFile?, _ error: NKError) -> Void) {
-
-        let requestBody =
-        """
-        <?xml version=\"1.0\" encoding=\"UTF-8\"?>
-        <d:propfind xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">
-            <d:prop></d:prop>
-        </d:propfind>
-        """
-
-        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrlFileName,
-                                             depth: "0",
-                                             requestBody: requestBody.data(using: .utf8),
-                                             options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
-
-            if error == .success, let file = files.first {
-                completion(account, true, file, error)
-            } else if error.errorCode == NCGlobal.shared.errorResourceNotFound {
-                completion(account, false, nil, error)
-            } else {
-                completion(account, nil, nil, error)
-            }
-        }
-    }
-
-    // MARK: - Synchronization ServerUrl
-
-    func synchronization(account: String,
-                         serverUrl: String,
-                         selector: String,
-                         completion: @escaping () -> Void = {}) {
-
-        let startDate = Date()
-
-        NextcloudKit.shared.readFileOrFolder(serverUrlFileName: serverUrl,
-                                             depth: "infinity",
-                                             showHiddenFiles: NCKeychain().showHiddenFiles,
-                                             options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, files, _, error in
-
-            if error == .success {
-                NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: true) { _, _, metadatas in
-                    for metadata in metadatas {
-                        autoreleasepool {
-                            if metadata.directory {
-                                NCManageDatabase.shared.addMetadata(metadata)
-                            } else if selector == NCGlobal.shared.selectorSynchronizationOffline,
-                                      metadata.isSynchronizable,
-                                      self.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
-                                self.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: selector))
-                            }
-                        }
-                    }
-                    let diffDate = Date().timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate
-                    NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Synchronization " + serverUrl + " in \(diffDate)")
-                    completion()
-                }
-            } else {
-                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Synchronization " + serverUrl + ", \(error.description)")
-                completion()
-            }
-        }
-    }
-
-    func synchronization(account: String, serverUrl: String, selector: String) async {
-
-        await withUnsafeContinuation({ continuation in
-            synchronization(account: account, serverUrl: serverUrl, selector: selector) {
-                continuation.resume(returning: ())
-            }
-        })
-    }
-
-    // MARK: - Search
-
-    /// WebDAV search
-    func searchFiles(urlBase: NCUserBaseUrl,
-                     literal: String,
-                     completion: @escaping (_ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
-
-        NextcloudKit.shared.searchLiteral(serverUrl: urlBase.urlBase,
-                                          depth: "infinity",
-                                          literal: literal,
-                                          showHiddenFiles: NCKeychain().showHiddenFiles,
-                                          options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { account, files, _, error in
-
-            guard error == .success else {
-                return completion(nil, error)
-            }
-
-            NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, metadatasFolder, metadatas in
-
-                // Update sub directories
-                for folder in metadatasFolder {
-                    let serverUrl = folder.serverUrl + "/" + folder.fileName
-                    NCManageDatabase.shared.addDirectory(encrypted: folder.e2eEncrypted, favorite: folder.favorite, ocId: folder.ocId, fileId: folder.fileId, etag: nil, permissions: folder.permissions, serverUrl: serverUrl, account: account)
-                }
-
-                NCManageDatabase.shared.addMetadatas(metadatas)
-                let metadatas = Array(metadatas.map(tableMetadata.init))
-                completion(metadatas, error)
-            }
-        }
-    }
-
-    /// Unified Search (NC>=20)
-    ///
-    func unifiedSearchFiles(userBaseUrl: NCUserBaseUrl,
-                            literal: String,
-                            providers: @escaping (_ accout: String, _ searchProviders: [NKSearchProvider]?) -> Void,
-                            update: @escaping (_ account: String, _ id: String, NKSearchResult?, [tableMetadata]?) -> Void,
-                            completion: @escaping (_ account: String, _ error: NKError) -> Void) {
-
-        let dispatchGroup = DispatchGroup()
-        dispatchGroup.enter()
-        dispatchGroup.notify(queue: .main) {
-            completion(userBaseUrl.account, NKError())
-        }
-
-        NextcloudKit.shared.unifiedSearch(term: literal, timeout: 30, timeoutProvider: 90) { _ in
-            // example filter
-            // ["calendar", "files", "fulltextsearch"].contains(provider.id)
-            return true
-        } request: { request in
-            if let request = request {
-                self.requestsUnifiedSearch.append(request)
-            }
-        } providers: { account, searchProviders in
-            providers(account, searchProviders)
-        } update: { account, partialResult, provider, _ in
-            guard let partialResult = partialResult else { return }
-            var metadatas: [tableMetadata] = []
-
-            switch provider.id {
-            case "files":
-                partialResult.entries.forEach({ entry in
-                    if let fileId = entry.fileId,
-                       let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
-                        metadatas.append(metadata)
-                    } else if let filePath = entry.filePath {
-                        let semaphore = DispatchSemaphore(value: 0)
-                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: dispatchGroup) { _, metadata, _ in
-                            metadatas.append(metadata)
-                            semaphore.signal()
-                        }
-                        semaphore.wait()
-                    } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
-                })
-            case "fulltextsearch":
-                // NOTE: FTS could also return attributes like files
-                // https://github.com/nextcloud/files_fulltextsearch/issues/143
-                partialResult.entries.forEach({ entry in
-                    let url = URLComponents(string: entry.resourceURL)
-                    guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
-                    if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(
-                              format: "account == %@ && path == %@ && fileName == %@",
-                              userBaseUrl.userAccount,
-                              "/remote.php/dav/files/" + userBaseUrl.user + dir,
-                              filename)) {
-                        metadatas.append(metadata)
-                    } else {
-                        let semaphore = DispatchSemaphore(value: 0)
-                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: dispatchGroup) { _, metadata, _ in
-                            metadatas.append(metadata)
-                            semaphore.signal()
-                        }
-                        semaphore.wait()
-                    }
-                })
-            default:
-                partialResult.entries.forEach({ entry in
-                    let metadata = NCManageDatabase.shared.createMetadata(account: userBaseUrl.account, user: userBaseUrl.user, userId: userBaseUrl.userId, fileName: entry.title, fileNameView: entry.title, ocId: NSUUID().uuidString, serverUrl: userBaseUrl.urlBase, urlBase: userBaseUrl.urlBase, url: entry.resourceURL, contentType: "", isUrl: true, name: partialResult.id, subline: entry.subline, iconName: entry.icon, iconUrl: entry.thumbnailURL)
-                    metadatas.append(metadata)
-                })
-            }
-            update(account, provider.id, partialResult, metadatas)
-        } completion: { _, _, _ in
-            self.requestsUnifiedSearch.removeAll()
-            dispatchGroup.leave()
-        }
-    }
-
-    func unifiedSearchFilesProvider(userBaseUrl: NCUserBaseUrl,
-                                    id: String, term: String,
-                                    limit: Int, cursor: Int,
-                                    completion: @escaping (_ account: String, _ searchResult: NKSearchResult?, _ metadatas: [tableMetadata]?, _ error: NKError) -> Void) {
-
-        var metadatas: [tableMetadata] = []
-
-        let request = NextcloudKit.shared.searchProvider(id, account: userBaseUrl.account, term: term, limit: limit, cursor: cursor, timeout: 60) { account, searchResult, _, error in
-            guard let searchResult = searchResult else {
-                completion(account, nil, metadatas, error)
-                return
-            }
-
-            switch id {
-            case "files":
-                searchResult.entries.forEach({ entry in
-                    if let fileId = entry.fileId, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && fileId == %@", userBaseUrl.userAccount, String(fileId))) {
-                        metadatas.append(metadata)
-                    } else if let filePath = entry.filePath {
-                        let semaphore = DispatchSemaphore(value: 0)
-                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: filePath, dispatchGroup: nil) { _, metadata, _ in
-                            metadatas.append(metadata)
-                            semaphore.signal()
-                        }
-                        semaphore.wait()
-                    } else { print(#function, "[ERROR]: File search entry has no path: \(entry)") }
-                })
-            case "fulltextsearch":
-                // NOTE: FTS could also return attributes like files
-                // https://github.com/nextcloud/files_fulltextsearch/issues/143
-                searchResult.entries.forEach({ entry in
-                    let url = URLComponents(string: entry.resourceURL)
-                    guard let dir = url?.queryItems?["dir"]?.value, let filename = url?.queryItems?["scrollto"]?.value else { return }
-                    if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ && path == %@ && fileName == %@", userBaseUrl.userAccount, "/remote.php/dav/files/" + userBaseUrl.user + dir, filename)) {
-                        metadatas.append(metadata)
-                    } else {
-                        let semaphore = DispatchSemaphore(value: 0)
-                        self.loadMetadata(userBaseUrl: userBaseUrl, filePath: dir + filename, dispatchGroup: nil) { _, metadata, _ in
-                            metadatas.append(metadata)
-                            semaphore.signal()
-                        }
-                        semaphore.wait()
-                    }
-                })
-            default:
-                searchResult.entries.forEach({ entry in
-                    let newMetadata = NCManageDatabase.shared.createMetadata(account: userBaseUrl.account, user: userBaseUrl.user, userId: userBaseUrl.userId, fileName: entry.title, fileNameView: entry.title, ocId: NSUUID().uuidString, serverUrl: userBaseUrl.urlBase, urlBase: userBaseUrl.urlBase, url: entry.resourceURL, contentType: "", isUrl: true, name: searchResult.name.lowercased(), subline: entry.subline, iconName: entry.icon, iconUrl: entry.thumbnailURL)
-                    metadatas.append(newMetadata)
-                })
-            }
-
-            completion(account, searchResult, metadatas, error)
-        }
-        if let request = request {
-            requestsUnifiedSearch.append(request)
-        }
-    }
-
-    func cancelUnifiedSearchFiles() {
-        for request in requestsUnifiedSearch {
-            request.cancel()
-        }
-        requestsUnifiedSearch.removeAll()
-    }
-
-    private func loadMetadata(userBaseUrl: NCUserBaseUrl,
-                              filePath: String,
-                              dispatchGroup: DispatchGroup? = nil,
-                              completion: @escaping (String, tableMetadata, NKError) -> Void) {
-
-        let urlPath = userBaseUrl.urlBase + "/remote.php/dav/files/" + userBaseUrl.user + filePath
-        dispatchGroup?.enter()
-        self.readFile(serverUrlFileName: urlPath) { account, metadata, error in
-            defer { dispatchGroup?.leave() }
-            guard let metadata = metadata else { return }
-            let returnMetadata = tableMetadata.init(value: metadata)
-            NCManageDatabase.shared.addMetadata(metadata)
-            completion(account, returnMetadata, error)
-        }
-    }
-
-    // MARK: - WebDav Create Folder
-
-    func createFolder(fileName: String,
-                      serverUrl: String,
-                      account: String,
-                      urlBase: String,
-                      userId: String,
-                      overwrite: Bool = false,
-                      withPush: Bool,
-                      completion: @escaping (_ error: NKError) -> Void) {
-
-        let isDirectoryEncrypted = utilityFileSystem.isDirectoryE2EE(account: account, urlBase: urlBase, userId: userId, serverUrl: serverUrl)
-        let fileName = fileName.trimmingCharacters(in: .whitespacesAndNewlines)
-
-        if isDirectoryEncrypted {
-#if !EXTENSION
-            Task {
-                let error = await NCNetworkingE2EECreateFolder().createFolder(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId, withPush: withPush)
-                completion(error)
-            }
-#endif
-        } else {
-            createFolderPlain(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, overwrite: overwrite, withPush: withPush, completion: completion)
-        }
-    }
-
-    private func createFolderPlain(fileName: String,
-                                   serverUrl: String,
-                                   account: String,
-                                   urlBase: String,
-                                   overwrite: Bool,
-                                   withPush: Bool,
-                                   completion: @escaping (_ error: NKError) -> Void) {
-
-        var fileNameFolder = utility.removeForbiddenCharacters(fileName)
-        if fileName != fileNameFolder {
-            let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), NCGlobal.shared.forbiddenCharacters.joined(separator: " "))
-            let error = NKError(errorCode: NCGlobal.shared.errorConflict, errorDescription: errorDescription)
-            return completion(error)
-        }
-
-        if !overwrite {
-            fileNameFolder = utilityFileSystem.createFileName(fileNameFolder, serverUrl: serverUrl, account: account)
-        }
-        if fileNameFolder.isEmpty {
-            return completion(NKError())
-        }
-        let fileNameFolderUrl = serverUrl + "/" + fileNameFolder
-
-        NextcloudKit.shared.createFolder(serverUrlFileName: fileNameFolderUrl) { account, _, _, error in
-            guard error == .success else {
-                if error.errorCode == NCGlobal.shared.errorMethodNotSupported && overwrite {
-                    completion(NKError())
-                } else {
-                    completion(error)
-                }
-                return
-            }
-
-            self.readFile(serverUrlFileName: fileNameFolderUrl) { account, metadataFolder, error in
-
-                if error == .success {
-                    if let metadata = metadataFolder {
-                        NCManageDatabase.shared.addMetadata(metadata)
-                        NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: fileNameFolderUrl, account: account)
-                    }
-                    if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadataFolder?.ocId) {
-                        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCreateFolder, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "withPush": withPush])
-                    }
-                }
-                completion(error)
-            }
-        }
-    }
-
-    func createFolder(assets: [PHAsset],
-                      selector: String,
-                      useSubFolder: Bool,
-                      account: String,
-                      urlBase: String,
-                      userId: String,
-                      withPush: Bool) -> Bool {
-
-        let autoUploadPath = NCManageDatabase.shared.getAccountAutoUploadPath(urlBase: urlBase, userId: userId, account: account)
-        let serverUrlBase = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: urlBase, userId: userId, account: account)
-        let fileNameBase = NCManageDatabase.shared.getAccountAutoUploadFileName()
-        let autoUploadSubfolderGranularity = NCManageDatabase.shared.getAccountAutoUploadSubfolderGranularity()
-
-        func createFolder(fileName: String, serverUrl: String) -> Bool {
-            var result: Bool = false
-            let semaphore = DispatchSemaphore(value: 0)
-            NCNetworking.shared.createFolder(fileName: fileName, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId, overwrite: true, withPush: withPush) { error in
-                if error == .success { result = true }
-                semaphore.signal()
-            }
-            semaphore.wait()
-            return result
-        }
-
-        func createNameSubFolder() -> [String] {
-
-            var datesSubFolder: [String] = []
-            let dateFormatter = DateFormatter()
-
-            for asset in assets {
-                let date = asset.creationDate ?? Date()
-                dateFormatter.dateFormat = "yyyy"
-                let year = dateFormatter.string(from: date)
-                dateFormatter.dateFormat = "MM"
-                let month = dateFormatter.string(from: date)
-                dateFormatter.dateFormat = "dd"
-                let day = dateFormatter.string(from: date)
-                if autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityYearly {
-                    datesSubFolder.append("\(year)")
-                } else if autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily {
-                    datesSubFolder.append("\(year)/\(month)/\(day)")
-                } else {  // Month Granularity is default
-                    datesSubFolder.append("\(year)/\(month)")
-                }
-            }
-
-            return Array(Set(datesSubFolder))
-        }
-
-        var result = createFolder(fileName: fileNameBase, serverUrl: serverUrlBase)
-
-        if useSubFolder && result {
-            for dateSubFolder in createNameSubFolder() {
-                let subfolderArray = dateSubFolder.split(separator: "/")
-                let year = subfolderArray[0]
-                let serverUrlYear = autoUploadPath
-                result = createFolder(fileName: String(year), serverUrl: serverUrlYear)  // Year always present independently of preference value
-                if result && autoUploadSubfolderGranularity >= NCGlobal.shared.subfolderGranularityMonthly {
-                    let month = subfolderArray[1]
-                    let serverUrlMonth = autoUploadPath + "/" + year
-                    result = createFolder(fileName: String(month), serverUrl: serverUrlMonth)
-                    if result && autoUploadSubfolderGranularity == NCGlobal.shared.subfolderGranularityDaily {
-                        let day = subfolderArray[2]
-                        let serverUrlDay = autoUploadPath + "/" + year + "/" + month
-                        result = createFolder(fileName: String(day), serverUrl: serverUrlDay)
-                    }
-                }
-                if !result { break }
-            }
-        }
-
-        return result
-    }
-
-    // MARK: - WebDav Delete
-
-    func deleteMetadata(_ metadata: tableMetadata, onlyLocalCache: Bool) async -> (NKError) {
-
-        if onlyLocalCache {
-
-#if !EXTENSION
-            NCActivityIndicator.shared.start()
-#endif
-
-            func delete(metadata: tableMetadata) {
-                if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
-                    NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "account == %@ AND ocId == %@", metadataLive.account, metadataLive.ocId))
-                    utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
-                }
-                NCManageDatabase.shared.deleteVideo(metadata: metadata)
-                NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "account == %@ AND ocId == %@", metadata.account, metadata.ocId))
-                utilityFileSystem.removeFile(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-            }
-
-            if metadata.directory {
-                let serverUrl = metadata.serverUrl + "/" + metadata.fileName
-                if let metadatas = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND directory == false", metadata.account, serverUrl)) {
-                    for metadata in metadatas {
-                        delete(metadata: metadata)
-                    }
-                }
-            } else {
-                delete(metadata: metadata)
-            }
-
-#if !EXTENSION
-            NCActivityIndicator.shared.stop()
-#endif
-            return NKError()
-        }
-
-        if metadata.isDirectoryE2EE {
-#if !EXTENSION
-            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
-                let error = await NCNetworkingE2EEDelete().delete(metadata: metadataLive)
-                if error == .success {
-                    return await NCNetworkingE2EEDelete().delete(metadata: metadata)
-                } else {
-                    return error
-                }
-            } else {
-                return await NCNetworkingE2EEDelete().delete(metadata: metadata)
-            }
-#else
-            return NKError()
-#endif
-        } else {
-            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
-                let error = await deleteMetadataPlain(metadataLive)
-                if error == .success {
-                    return await deleteMetadataPlain(metadata)
-                } else {
-                    return error
-                }
-            } else {
-                return await deleteMetadataPlain(metadata)
-            }
-        }
-    }
-
-    func deleteMetadataPlain(_ metadata: tableMetadata, customHeader: [String: String]? = nil) async -> NKError {
-
-        // verify permission
-        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanDelete)
-        if !metadata.permissions.isEmpty && permission == false {
-            return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_delete_file_")
-        }
-        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
-        let options = NKRequestOptions(customHeader: customHeader)
-
-        let result = await NextcloudKit.shared.deleteFileOrFolder(serverUrlFileName: serverUrlFileName, options: options)
-        if result.error == .success || result.error.errorCode == NCGlobal.shared.errorResourceNotFound {
-
-            do {
-                try FileManager.default.removeItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-            } catch { }
-
-            NCManageDatabase.shared.deleteVideo(metadata: metadata)
-            NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-            NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-            // LIVE PHOTO SERVER
-            if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
-                do {
-                    try FileManager.default.removeItem(atPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
-                } catch { }
-
-                NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
-                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
-                NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
-            }
-
-            if metadata.directory {
-                NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: metadata.account)
-            }
-        }
-        return result.error
-    }
-
-    // MARK: - WebDav Favorite
-
-    func favoriteMetadata(_ metadata: tableMetadata,
-                          completion: @escaping (_ error: NKError) -> Void) {
-
-        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) {
-            favoriteMetadataPlain(metadataLive) { error in
-                if error == .success {
-                    self.favoriteMetadataPlain(metadata, completion: completion)
-                } else {
-                    completion(error)
-                }
-            }
-        } else {
-            favoriteMetadataPlain(metadata, completion: completion)
-        }
-    }
-
-    private func favoriteMetadataPlain(_ metadata: tableMetadata,
-                                       completion: @escaping (_ error: NKError) -> Void) {
-
-        let fileName = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
-        let favorite = !metadata.favorite
-        let ocId = metadata.ocId
-
-        NextcloudKit.shared.setFavorite(fileName: fileName, favorite: favorite) { account, error in
-            if error == .success && metadata.account == account {
-                NCManageDatabase.shared.setMetadataFavorite(ocId: metadata.ocId, favorite: favorite)
-                if favorite, metadata.directory {
-                    let serverUrl = metadata.serverUrl + "/" + metadata.fileName
-                    self.synchronization(account: metadata.account, serverUrl: serverUrl, selector: NCGlobal.shared.selectorSynchronizationFavorite)
-                }
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterFavoriteFile, userInfo: ["ocId": ocId, "serverUrl": metadata.serverUrl])
-            }
-            completion(error)
-        }
-    }
-
-    // MARK: - Lock Files
-
-    func lockUnlockFile(_ metadata: tableMetadata, shoulLock: Bool) {
-
-        NextcloudKit.shared.lockUnlockFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName, shouldLock: shoulLock) { _, error in
-            // 0: lock was successful; 412: lock did not change, no error, refresh
-            guard error == .success || error.errorCode == NCGlobal.shared.errorPreconditionFailed else {
-                let error = NKError(errorCode: error.errorCode, errorDescription: "_files_lock_error_")
-                NCContentPresenter().messageNotification(metadata.fileName, error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
-                return
-            }
-            NCNetworking.shared.readFile(serverUrlFileName: metadata.serverUrl + "/" + metadata.fileName) { _, metadata, error in
-                guard error == .success, let metadata = metadata else { return }
-                NCManageDatabase.shared.addMetadata(metadata)
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
-            }
-        }
-    }
-
-    // MARK: - WebDav Rename
-
-    func renameMetadata(_ metadata: tableMetadata,
-                        fileNameNew: String,
-                        indexPath: IndexPath,
-                        viewController: UIViewController?,
-                        completion: @escaping (_ error: NKError) -> Void) {
-
-        let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata)
-        let fileNameNew = fileNameNew.trimmingCharacters(in: .whitespacesAndNewlines)
-        let fileNameNewLive = (fileNameNew as NSString).deletingPathExtension + ".mov"
-
-        if metadata.isDirectoryE2EE {
-#if !EXTENSION
-            Task {
-                if let metadataLive = metadataLive {
-                    let error = await NCNetworkingE2EERename().rename(metadata: metadataLive, fileNameNew: fileNameNew, indexPath: indexPath)
-                    if error == .success {
-                        let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
-                        DispatchQueue.main.async { completion(error) }
-                    } else {
-                        DispatchQueue.main.async { completion(error) }
-                    }
-                } else {
-                    let error = await NCNetworkingE2EERename().rename(metadata: metadata, fileNameNew: fileNameNew, indexPath: indexPath)
-                    DispatchQueue.main.async { completion(error) }
-                }
-            }
-#endif
-        } else {
-            if let metadataLive, metadata.isNotFlaggedAsLivePhotoByServer {
-                renameMetadataPlain(metadataLive, fileNameNew: fileNameNewLive, indexPath: indexPath) { error in
-                    if error == .success {
-                        self.renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
-                    } else {
-                        completion(error)
-                    }
-                }
-            } else {
-                renameMetadataPlain(metadata, fileNameNew: fileNameNew, indexPath: indexPath, completion: completion)
-            }
-        }
-    }
-
-    private func renameMetadataPlain(_ metadata: tableMetadata,
-                                     fileNameNew: String,
-                                     indexPath: IndexPath,
-                                     completion: @escaping (_ error: NKError) -> Void) {
-
-        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanRename)
-        if !metadata.permissions.isEmpty && !permission {
-            return completion(NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_"))
-        }
-        let fileName = utility.removeForbiddenCharacters(fileNameNew)
-        if fileName != fileNameNew {
-            let errorDescription = String(format: NSLocalizedString("_forbidden_characters_", comment: ""), NCGlobal.shared.forbiddenCharacters.joined(separator: " "))
-            let error = NKError(errorCode: NCGlobal.shared.errorConflict, errorDescription: errorDescription)
-            return completion(error)
-        }
-        let fileNameNew = fileName
-        if fileNameNew.isEmpty || fileNameNew == metadata.fileNameView {
-            return completion(NKError())
-        }
-        let fileNamePath = metadata.serverUrl + "/" + metadata.fileName
-        let fileNameToPath = metadata.serverUrl + "/" + fileNameNew
-
-        NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: fileNamePath, serverUrlFileNameDestination: fileNameToPath, overwrite: false) { _, error in
-            if error == .success {
-                if metadata.directory {
-                    let serverUrl = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
-                    let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: fileNameNew)
-                    if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) {
-                        NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, serverUrlTo: serverUrlTo, etag: "", ocId: nil, fileId: nil, encrypted: directory.e2eEncrypted, richWorkspace: nil, account: metadata.account)
-                    }
-                } else {
-                    do {
-                        try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-                    } catch { }
-                    NCManageDatabase.shared.deleteVideo(metadata: metadata)
-                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                    NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                    // LIVE PHOTO SERVER
-                    if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
-                        do {
-                            try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
-                        } catch { }
-                        NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
-                        NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
-                        NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
-                    }
-                }
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["ocId": metadata.ocId, "account": metadata.account, "indexPath": indexPath])
-            }
-            completion(error)
-        }
-    }
-
-    // MARK: - WebDav Move
-
-    func moveMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
-
-        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
-            let error = await moveMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
-            if error == .success {
-                return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
-            } else {
-                return error
-            }
-        }
-        return await moveMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
-    }
-
-    private func moveMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
-
-        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanRename)
-        if !metadata.permissions.isEmpty && !permission {
-            return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
-        }
-        let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
-        let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
-
-        let result = await NextcloudKit.shared.moveFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite)
-        if result.error == .success {
-            if metadata.directory {
-                NCManageDatabase.shared.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: result.account)
-            } else {
-                do {
-                    try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
-                } catch { }
-                NCManageDatabase.shared.deleteVideo(metadata: metadata)
-                NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
-                // LIVE PHOTO SERVER
-                if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadataLive.isFlaggedAsLivePhotoByServer {
-                    do {
-                        try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
-                    } catch { }
-                    NCManageDatabase.shared.deleteVideo(metadata: metadataLive)
-                    NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
-                    NCManageDatabase.shared.deleteLocalFile(predicate: NSPredicate(format: "ocId == %@", metadataLive.ocId))
-                }
-            }
-        }
-        return result.error
-    }
-
-    // MARK: - WebDav Copy
-
-    func copyMetadata(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
-
-        if let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata), metadata.isNotFlaggedAsLivePhotoByServer {
-            let error = await copyMetadataPlain(metadataLive, serverUrlTo: serverUrlTo, overwrite: overwrite)
-            if error == .success {
-                return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
-            } else {
-                return error
-            }
-        }
-        return await copyMetadataPlain(metadata, serverUrlTo: serverUrlTo, overwrite: overwrite)
-    }
-
-    private func copyMetadataPlain(_ metadata: tableMetadata, serverUrlTo: String, overwrite: Bool) async -> NKError {
-
-        let permission = utility.permissionsContainsString(metadata.permissions, permissions: NCGlobal.shared.permissionCanRename)
-        if !metadata.permissions.isEmpty && !permission {
-            return NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_no_permission_modify_file_")
-        }
-        let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
-        let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
-
-        let result = await NextcloudKit.shared.copyFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite)
-        return result.error
-    }
-
-    // MARK: - Direct Download
-
-    func getVideoUrl(metadata: tableMetadata,
-                     completition: @escaping (_ url: URL?, _ autoplay: Bool, _ error: NKError) -> Void) {
-
-        if !metadata.url.isEmpty {
-            if metadata.url.hasPrefix("/") {
-                completition(URL(fileURLWithPath: metadata.url), true, .success)
-            } else {
-                completition(URL(string: metadata.url), true, .success)
-            }
-        } else if utilityFileSystem.fileProviderStorageExists(metadata) {
-            completition(URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)), false, .success)
-        } else {
-            NextcloudKit.shared.getDirectDownload(fileId: metadata.fileId) { _, url, _, error in
-                if error == .success && url != nil {
-                    if let url = URL(string: url!) {
-                        completition(url, false, error)
-                    } else {
-                        completition(nil, false, error)
-                    }
-                } else {
-                    completition(nil, false, error)
-                }
-            }
-        }
-    }
-
-    // MARK: - [NextcloudKit wrapper] convert completion handlers into async functions
-
-    func getPreview(url: URL,
-                    options: NKRequestOptions = NKRequestOptions()) async -> (account: String, data: Data?, error: NKError) {
-
-        await withUnsafeContinuation({ continuation in
-            NextcloudKit.shared.getPreview(url: url, options: options) { account, data, error in
-                continuation.resume(returning: (account: account, data: data, error: error))
-            }
-        })
-    }
-
-    func downloadPreview(fileNamePathOrFileId: String,
-                         fileNamePreviewLocalPath: String,
-                         widthPreview: Int,
-                         heightPreview: Int,
-                         fileNameIconLocalPath: String? = nil,
-                         sizeIcon: Int = 0,
-                         etag: String? = nil,
-                         endpointTrashbin: Bool = false,
-                         useInternalEndpoint: Bool = true,
-                         options: NKRequestOptions = NKRequestOptions()) async -> (account: String, imagePreview: UIImage?, imageIcon: UIImage?, imageOriginal: UIImage?, etag: String?, error: NKError) {
-
-        await withUnsafeContinuation({ continuation in
-            NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId, fileNamePreviewLocalPath: fileNamePreviewLocalPath, widthPreview: widthPreview, heightPreview: heightPreview, fileNameIconLocalPath: fileNameIconLocalPath, sizeIcon: sizeIcon, etag: etag, options: options) { account, imagePreview, imageIcon, imageOriginal, etag, error in
-                continuation.resume(returning: (account: account, imagePreview: imagePreview, imageIcon: imageIcon, imageOriginal: imageOriginal, etag: etag, error: error))
-            }
-        })
-    }
-}
-
-extension Array where Element == URLQueryItem {
-    subscript(name: String) -> URLQueryItem? {
-        first(where: { $0.name == name })
-    }
-}
-
-// MARK: -
-
-class NCOperationDownload: ConcurrentOperation {
-
-    var metadata: tableMetadata
-    var selector: String
-
-    init(metadata: tableMetadata, selector: String) {
-        self.metadata = tableMetadata.init(value: metadata)
-        self.selector = selector
-    }
-
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-
-        metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
-        metadata.sessionError = ""
-        metadata.sessionSelector = selector
-        metadata.sessionTaskIdentifier = 0
-        metadata.status = NCGlobal.shared.metadataStatusWaitDownload
-
-        NCManageDatabase.shared.addMetadata(metadata)
-
-        NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
-        } completion: { _, _ in
-            self.finish()
-        }
-    }
-}
-
-class NCOperationConvertLivePhoto: ConcurrentOperation {
-
-    var serverUrlfileNamePath, livePhotoFile, account, ocId: String
-
-    init(serverUrlfileNamePath: String, livePhotoFile: String, account: String, ocId: String) {
-        self.serverUrlfileNamePath = serverUrlfileNamePath
-        self.livePhotoFile = livePhotoFile
-        self.account = account
-        self.ocId = ocId
-    }
-
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-        NextcloudKit.shared.setLivephoto(serverUrlfileNamePath: serverUrlfileNamePath, livePhotoFile: livePhotoFile, options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, error in
-            if error == .success {
-                NCManageDatabase.shared.setMetadataLivePhotoByServer(account: self.account, ocId: self.ocId, livePhotoFile: self.livePhotoFile)
-            } else {
-                NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Convert LivePhoto with error \(error.errorCode)")
-            }
-            self.finish()
-            if NCNetworking.shared.convertLivePhotoQueue.operationCount == 0 {
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, second: 0.1)
-            }
-        }
-    }
-}
-
-class NCOperationDownloadAvatar: ConcurrentOperation {
-
-    var user: String
-    var fileName: String
-    var etag: String?
-    var fileNameLocalPath: String
-    var cell: NCCellProtocol!
-    var view: UIView?
-    var cellImageView: UIImageView?
-
-    init(user: String, fileName: String, fileNameLocalPath: String, cell: NCCellProtocol, view: UIView?, cellImageView: UIImageView?) {
-        self.user = user
-        self.fileName = fileName
-        self.fileNameLocalPath = fileNameLocalPath
-        self.cell = cell
-        self.view = view
-        self.etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag
-        self.cellImageView = cellImageView
-    }
-
-    override func start() {
-
-        guard !isCancelled else { return self.finish() }
-
-        NextcloudKit.shared.downloadAvatar(user: user,
-                                           fileNameLocalPath: fileNameLocalPath,
-                                           sizeImage: NCGlobal.shared.avatarSize,
-                                           avatarSizeRounded: NCGlobal.shared.avatarSizeRounded,
-                                           etag: self.etag,
-                                           options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { _, imageAvatar, _, etag, error in
-
-            if error == .success, let imageAvatar = imageAvatar, let etag = etag {
-                NCManageDatabase.shared.addAvatar(fileName: self.fileName, etag: etag)
-                DispatchQueue.main.async {
-                    if self.user == self.cell.fileUser, let avatarImageView = self.cellImageView {
-                        UIView.transition(with: avatarImageView,
-                                          duration: 0.75,
-                                          options: .transitionCrossDissolve,
-                                          animations: { avatarImageView.image = imageAvatar },
-                                          completion: nil)
-                    } else {
-                        if self.view is UICollectionView {
-                            (self.view as? UICollectionView)?.reloadData()
-                        } else if self.view is UITableView {
-                            (self.view as? UITableView)?.reloadData()
-                        }
-                    }
-                }
-            } else if error.errorCode == NCGlobal.shared.errorNotModified {
-                NCManageDatabase.shared.setAvatarLoaded(fileName: self.fileName)
-            }
-            self.finish()
-        }
-    }
 }

+ 1 - 5
iOSClient/Networking/NCNetworkingCheckRemoteUser.swift

@@ -33,11 +33,7 @@ class NCNetworkingCheckRemoteUser {
               !token.isEmpty,
               let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
 
-        NCNetworking.shared.cancelAllQueue()
-        NCNetworking.shared.cancelDataTask()
-        NCNetworking.shared.cancelDownloadTasks()
-        NCNetworking.shared.cancelUploadTasks()
-        NCNetworking.shared.cancelUploadBackgroundTask(withNotification: false)
+        NCNetworking.shared.cancelAllTask()
 
         if NCGlobal.shared.capabilityServerVersionMajor >= NCGlobal.shared.nextcloudVersion17 {
 

+ 86 - 43
iOSClient/Networking/NCNetworkingProcessUpload.swift → iOSClient/Networking/NCNetworkingProcess.swift

@@ -1,5 +1,5 @@
 //
-//  NCNetworkingProcessUpload.swift
+//  NCNetworkingProcess.swift
 //  Nextcloud
 //
 //  Created by Marino Faggiana on 25/06/2020.
@@ -27,13 +27,14 @@ import Photos
 import JGProgressHUD
 import RealmSwift
 
-class NCNetworkingProcessUpload: NSObject {
-    public static let shared: NCNetworkingProcessUpload = {
-        let instance = NCNetworkingProcessUpload()
+class NCNetworkingProcess: NSObject {
+    public static let shared: NCNetworkingProcess = {
+        let instance = NCNetworkingProcess()
         return instance
     }()
 
     private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    private let utilityFileSystem = NCUtilityFileSystem()
     private lazy var rootViewController = appDelegate.window?.rootViewController
     private lazy var hudView = rootViewController?.view
     private var notificationToken: NotificationToken?
@@ -48,12 +49,11 @@ class NCNetworkingProcessUpload: NSObject {
             notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
                 switch changes {
                 case .initial:
-                    print("Initial")
+                    break
                 case .update(_, let deletions, let insertions, let modifications):
                     if !deletions.isEmpty || !insertions.isEmpty || !modifications.isEmpty {
                         self?.invalidateObserveTableMetadata()
-                        self?.start(completition: { items in
-                            print("[LOG] PROCESS-UPLOAD-OBSERVE \(items)")
+                        self?.start(completition: { _, _ in
                             DispatchQueue.main.async {
                                 self?.observeTableMetadata()
                             }
@@ -87,21 +87,23 @@ class NCNetworkingProcessUpload: NSObject {
     }
 
     @objc private func processTimer() {
-        start { items in
-            print("[LOG] PROCESS-UPLOAD-TIMER \(items)")
+        start { itemsDownload, itemsUpload in
+            print("[LOG] PROCESS (TIMER) Download: \(itemsDownload) Upload: \(itemsUpload)")
+            NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber), object: nil)
         }
     }
 
-    func start(completition: @escaping (_ items: Int) -> Void) {
+    func start(completition: @escaping (_ itemsDownload: Int, _ itemsUpload: Int) -> Void) {
 
         if appDelegate.account.isEmpty || pauseProcess {
-            return completition(0)
+            return completition(0, 0)
         } else {
             pauseProcess = true
         }
 
         let applicationState = UIApplication.shared.applicationState
         let queue = DispatchQueue.global()
+        let maxConcurrentOperationDownload = NCBrandOptions.shared.maxConcurrentOperationDownload
         var maxConcurrentOperationUpload = NCBrandOptions.shared.maxConcurrentOperationUpload
 
         if applicationState == .active {
@@ -110,40 +112,70 @@ class NCNetworkingProcessUpload: NSObject {
 
         queue.async {
 
-            let metadatasUpload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploading))
+            let metadatasDownloading = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusDownloading))
+            let metadatasUploading = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploading))
             let isWiFi = NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi
-            var counterUpload = metadatasUpload.count
-            let sessionSelectors = [NCGlobal.shared.selectorUploadFileNODelete, NCGlobal.shared.selectorUploadFile, NCGlobal.shared.selectorUploadAutoUpload, NCGlobal.shared.selectorUploadAutoUploadAll]
+            var counterDownload = metadatasDownloading.count
+            var counterUpload = metadatasUploading.count
+            let semaphore = DispatchSemaphore(value: 0)
+
+            // DOWNLOAD
+            //
+
+            let limitDownload = maxConcurrentOperationDownload - counterDownload
+            let metadatasDownload = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@ AND session == %@ AND status == %d", self.appDelegate.account, NCNetworking.shared.sessionDownloadBackground, NCGlobal.shared.metadataStatusWaitDownload), page: 1, limit: limitDownload, sorted: "sessionDate", ascending: true)
+            for metadata in metadatasDownload where counterDownload < maxConcurrentOperationDownload {
+                NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
+                } completion: { _, _ in
+                    counterDownload += 1
+                    semaphore.signal()
+                }
+                semaphore.wait()
+            }
+            if counterDownload == 0 {
+                let metadatasDownloadInError: [tableMetadata] = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND session == %@ AND status == %d", self.appDelegate.account, NCNetworking.shared.sessionDownloadBackground, NCGlobal.shared.metadataStatusDownloadError), sorted: "sessionDate", ascending: true) ?? []
+                for metadata in metadatasDownloadInError {
+                    NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                               sessionError: "",
+                                                               status: NCGlobal.shared.metadataStatusWaitDownload)
+                }
+            }
+
+            // UPLOAD
+            //
 
             // ** TEST ONLY ONE **
             // E2EE
-            let uniqueMetadatas = metadatasUpload.unique(map: { $0.serverUrl })
+            let uniqueMetadatas = metadatasUploading.unique(map: { $0.serverUrl })
             for metadata in uniqueMetadatas {
                 if metadata.isDirectoryE2EE {
                     self.pauseProcess = false
-                    return completition(counterUpload)
+                    return completition(counterDownload, counterUpload)
                 }
             }
             // CHUNK
-            if !metadatasUpload.filter({ $0.chunk > 0 }).isEmpty {
+            if !metadatasUploading.filter({ $0.chunk > 0 }).isEmpty {
                 self.pauseProcess = false
-                return completition(counterUpload)
+                return completition(counterDownload, counterUpload)
             }
 
-            NCNetworking.shared.getOcIdInBackgroundSession(queue: queue, completion: { listOcId in
+            NCNetworking.shared.getUploadBackgroundSession(queue: queue, completion: { filesNameLocalPath in
+
+                let sessionUploadSelectors = [NCGlobal.shared.selectorUploadFileNODelete, NCGlobal.shared.selectorUploadFile, NCGlobal.shared.selectorUploadAutoUpload, NCGlobal.shared.selectorUploadAutoUploadAll]
 
-                for sessionSelector in sessionSelectors where counterUpload < maxConcurrentOperationUpload {
+                for sessionSelector in sessionUploadSelectors where counterUpload < maxConcurrentOperationUpload {
 
-                    let limit = maxConcurrentOperationUpload - counterUpload
-                    let metadatas = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@ AND sessionSelector == %@ AND status == %d", self.appDelegate.account, sessionSelector, NCGlobal.shared.metadataStatusWaitUpload), page: 1, limit: limit, sorted: "date", ascending: true)
-                    if !metadatas.isEmpty {
-                        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] PROCESS-UPLOAD find \(metadatas.count) items")
+                    let limitUpload = maxConcurrentOperationUpload - counterUpload
+                    let metadatasUpload = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@ AND sessionSelector == %@ AND status == %d", self.appDelegate.account, sessionSelector, NCGlobal.shared.metadataStatusWaitUpload), page: 1, limit: limitUpload, sorted: "sessionDate", ascending: true)
+                    if !metadatasUpload.isEmpty {
+                        NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] PROCESS (UPLOAD) find \(metadatasUpload.count) items")
                     }
 
-                    for metadata in metadatas where counterUpload < maxConcurrentOperationUpload {
+                    for metadata in metadatasUpload where counterUpload < maxConcurrentOperationUpload {
 
                         // Is already in upload background? skipped
-                        if listOcId.contains(metadata.ocId) {
+                        let fileNameLocalPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
+                        if filesNameLocalPath.contains(fileNameLocalPath) {
                             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Process auto upload skipped file: \(metadata.serverUrl)/\(metadata.fileNameView), because is already in session.")
                             continue
                         }
@@ -153,7 +185,6 @@ class NCNetworkingProcessUpload: NSObject {
                             continue
                         }
 
-                        let semaphore = DispatchSemaphore(value: 0)
                         let cameraRoll = NCCameraRoll()
                         cameraRoll.extractCameraRoll(from: metadata) { metadatas in
                             if metadatas.isEmpty {
@@ -187,15 +218,10 @@ class NCNetworkingProcessUpload: NSObject {
                     }
                 }
 
-                // Update Badge Number
-                NotificationCenter.default.post(
-                    name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber),
-                    object: nil)
-
                 // No upload available ? --> Retry Upload in Error
                 if counterUpload == 0 {
-                    let metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploadError))
-                    for metadata in metadatas {
+                    let metadatasUploadInError: [tableMetadata] = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploadError), sorted: "sessionDate", ascending: true) ?? []
+                    for metadata in metadatasUploadInError {
                         // Verify QUOTA
                         if metadata.sessionError.contains("\(NCGlobal.shared.errorQuota)") {
                             NextcloudKit.shared.getUserProfile { _, userProfile, _, error in
@@ -215,7 +241,7 @@ class NCNetworkingProcessUpload: NSObject {
                     }
 
                     // verify delete Asset Local Identifiers in auto upload (DELETE Photos album)
-                    if applicationState == .active && metadatas.isEmpty {
+                    if applicationState == .active && metadatasUploadInError.isEmpty {
                         self.deleteAssetLocalIdentifiers(account: self.appDelegate.account) {
                             self.pauseProcess = false
                         }
@@ -226,7 +252,7 @@ class NCNetworkingProcessUpload: NSObject {
                     self.pauseProcess = false
                 }
 
-                completition(counterUpload)
+                completition(counterDownload, counterUpload)
             })
         }
     }
@@ -235,7 +261,7 @@ class NCNetworkingProcessUpload: NSObject {
 
         DispatchQueue.main.async {
 
-            guard !self.appDelegate.isPasscodePresented else {
+            guard !NCPasscode.shared.isPasscodePresented else {
                 return completition()
             }
 
@@ -258,7 +284,7 @@ class NCNetworkingProcessUpload: NSObject {
 
     // MARK: -
 
-    func createProcessUploads(metadatas: [tableMetadata], verifyAlreadyExists: Bool = false, completion: @escaping (_ items: Int) -> Void) {
+    func createProcessUploads(metadatas: [tableMetadata], verifyAlreadyExists: Bool = false, completion: @escaping (_ items: Int) -> Void = {_ in}) {
 
         var metadatasForUpload: [tableMetadata] = []
         for metadata in metadatas {
@@ -278,7 +304,6 @@ class NCNetworkingProcessUpload: NSObject {
     func verifyUploadZombie() {
 
         Task {
-            let utilityFileSystem = NCUtilityFileSystem()
             var notificationCenter: Bool = false
 
             // selectorUploadFileShareExtension (FOREGROUND)
@@ -310,15 +335,14 @@ class NCNetworkingProcessUpload: NSObject {
                                                                session: "",
                                                                sessionError: "",
                                                                selector: "",
-                                                               status: NCGlobal.shared.metadataStatusNormal,
-                                                               errorCode: 0)
+                                                               status: NCGlobal.shared.metadataStatusNormal)
                     notificationCenter = true
                 }
             }
 
             // metadataStatusUploading (BACKGROUND)
-            let results = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "(session == %@ OR session == %@ OR session == %@) AND status == %d", NCNetworking.shared.sessionUploadBackground, NCNetworking.shared.sessionUploadBackgroundWWan, NCNetworking.shared.sessionUploadBackgroundExtension, NCGlobal.shared.metadataStatusUploading))
-            for metadata in results {
+            let resultsUpload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "(session == %@ OR session == %@ OR session == %@) AND status == %d", NCNetworking.shared.sessionUploadBackground, NCNetworking.shared.sessionUploadBackgroundWWan, NCNetworking.shared.sessionUploadBackgroundExtension, NCGlobal.shared.metadataStatusUploading))
+            for metadata in resultsUpload {
                 var taskUpload: URLSessionTask?
                 var session: URLSession?
                 if metadata.session == NCNetworking.shared.sessionUploadBackground {
@@ -340,6 +364,25 @@ class NCNetworkingProcessUpload: NSObject {
                 }
             }
 
+            // metadataStatusDowloading (BACKGROUND)
+            let resultsDownload = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "session == %@ AND status == %d", NCNetworking.shared.sessionDownloadBackground, NCGlobal.shared.metadataStatusDownloading))
+            for metadata in resultsDownload {
+                var taskDownload: URLSessionTask?
+                let session: URLSession? = NCNetworking.shared.sessionManagerDownloadBackground
+                if let tasks = await session?.allTasks {
+                    for task in tasks {
+                        if task.taskIdentifier == metadata.sessionTaskIdentifier { taskDownload = task }
+                    }
+                    if taskDownload == nil, let metadata = NCManageDatabase.shared.getResultMetadata(predicate: NSPredicate(format: "ocId == %@ AND status == %d", metadata.ocId, NCGlobal.shared.metadataStatusDownloading)) {
+                        NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
+                                                                   session: NCNetworking.shared.sessionDownloadBackground,
+                                                                   sessionError: "",
+                                                                   status: NCGlobal.shared.metadataStatusWaitDownload)
+                        notificationCenter = true
+                    }
+                }
+            }
+
             if notificationCenter {
                 NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
             }

+ 6 - 5
iOSClient/Networking/NCService.swift

@@ -50,7 +50,7 @@ class NCService: NSObject {
                 requestServerCapabilities()
                 requestDashboardWidget()
                 NCNetworkingE2EE().unlockAll(account: account)
-                NCNetworkingProcessUpload.shared.verifyUploadZombie()
+                NCNetworkingProcess.shared.verifyUploadZombie()
                 sendClientDiagnosticsRemoteOperation(account: account)
             }
         }
@@ -291,7 +291,7 @@ class NCService: NSObject {
         Task {
             if let directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "account == %@ AND offline == true", account), sorted: "serverUrl", ascending: true) {
                 for directory: tableDirectory in directories {
-                    await NCNetworking.shared.synchronization(account: account, serverUrl: directory.serverUrl, selector: NCGlobal.shared.selectorSynchronizationOffline)
+                    await NCNetworking.shared.synchronization(account: account, serverUrl: directory.serverUrl, add: false)
                 }
             }
         }
@@ -300,9 +300,10 @@ class NCService: NSObject {
         let files = NCManageDatabase.shared.getTableLocalFiles(predicate: NSPredicate(format: "account == %@ AND offline == true", account), sorted: "fileName", ascending: true)
         for file: tableLocalFile in files {
             guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(file.ocId) else { continue }
-            if metadata.isSynchronizable,
-               NCNetworking.shared.downloadQueue.operations.filter({ ($0 as? NCOperationDownload)?.metadata.ocId == metadata.ocId }).isEmpty {
-                NCNetworking.shared.downloadQueue.addOperation(NCOperationDownload(metadata: metadata, selector: NCGlobal.shared.selectorDownloadFile))
+            if NCNetworking.shared.isSynchronizable(ocId: metadata.ocId, fileName: metadata.fileName, etag: metadata.etag) {
+                NCManageDatabase.shared.setMetadatasSessionInWaitDownload(metadatas: [metadata],
+                                                                          session: NCNetworking.shared.sessionDownloadBackground,
+                                                                          selector: NCGlobal.shared.selectorSynchronizationOffline)
             }
         }
     }

+ 0 - 1
iOSClient/Offline/NCOffline.swift

@@ -34,7 +34,6 @@ class NCOffline: NCCollectionViewCommon {
         titleCurrentFolder = NSLocalizedString("_manage_file_offline_", comment: "")
         layoutKey = NCGlobal.shared.layoutViewOffline
         enableSearchBar = false
-        headerMenuButtonsView = true
         headerRichWorkspaceDisable = true
         emptyImage = UIImage(named: "folder")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)
         emptyTitle = "_files_no_files_"

+ 0 - 1
iOSClient/Recent/NCRecent.swift

@@ -34,7 +34,6 @@ class NCRecent: NCCollectionViewCommon {
         titleCurrentFolder = NSLocalizedString("_recent_", comment: "")
         layoutKey = NCGlobal.shared.layoutViewRecent
         enableSearchBar = false
-        headerMenuButtonsView = false
         headerRichWorkspaceDisable = true
         emptyImage = utility.loadImage(named: "clock.arrow.circlepath", color: .gray, size: UIScreen.main.bounds.width)
         emptyTitle = "_files_no_files_"

+ 9 - 0
iOSClient/RichWorkspace/NCViewerRichWorkspaceWebView.swift

@@ -51,6 +51,15 @@ class NCViewerRichWorkspaceWebView: UIViewController, WKNavigationDelegate, WKSc
         webView.load(request)
     }
 
+    deinit {
+        print("dealloc")
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        webView.configuration.userContentController.removeScriptMessageHandler(forName: "DirectEditingMobileInterface")
+    }
+
     @objc func keyboardDidShow(notification: Notification) {
         let window = UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow }
         let safeAreaInsetsBottom = window?.safeAreaInsets.bottom ?? 0

+ 2 - 1
iOSClient/Scan document/NCUploadScanDocument.swift

@@ -85,6 +85,7 @@ class NCUploadScanDocument: ObservableObject {
         metadata.session = NCNetworking.shared.sessionUploadBackground
         metadata.sessionSelector = NCGlobal.shared.selectorUploadFile
         metadata.status = NCGlobal.shared.metadataStatusWaitUpload
+        metadata.sessionDate = Date()
 
         if NCManageDatabase.shared.getMetadataConflict(account: userBaseUrl.account, serverUrl: serverUrl, fileNameView: fileName) != nil {
             completion(true, false)
@@ -126,7 +127,7 @@ class NCUploadScanDocument: ObservableObject {
             do {
                 try pdfData.write(to: URL(fileURLWithPath: fileNamePath), options: .atomic)
                 metadata.size = self.utilityFileSystem.getFileSize(filePath: fileNamePath)
-                NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: [metadata], completion: { _ in })
+                NCNetworkingProcess.shared.createProcessUploads(metadatas: [metadata])
                 if self.removeAllFiles {
                     let path = self.utilityFileSystem.directoryScan
                     let filePaths = try FileManager.default.contentsOfDirectory(atPath: path)

+ 1 - 1
iOSClient/Select/NCSelect.swift

@@ -442,7 +442,7 @@ extension NCSelect: UICollectionViewDataSource {
             self.headerMenu = header
 
             header.delegate = self
-            header.setButtonsView(height: 0)
+//            header.setButtonsView(height: 0)
             header.setRichWorkspaceHeight(heightHeaderRichWorkspace)
             header.setRichWorkspaceText(richWorkspaceText)
             header.setViewTransfer(isHidden: true)

+ 1 - 6
iOSClient/Settings/CCAdvanced.m

@@ -23,7 +23,6 @@
 
 #import "CCAdvanced.h"
 #import "CCUtility.h"
-#import "NSNotificationCenter+MainThread.h"
 #import "NCBridgeSwift.h"
 
 @interface CCAdvanced ()
@@ -355,11 +354,7 @@
 
 - (void)clearCache:(NSString *)account
 {
-    [[NCNetworking shared] cancelAllQueue];
-    [[NCNetworking shared] cancelDataTask];
-    [[NCNetworking shared] cancelDownloadTasks];
-    [[NCNetworking shared] cancelUploadTasks];
-    [[NCNetworking shared] cancelUploadBackgroundTaskWithNotification:false];
+    [[NCNetworking shared] cancelAllTask];
 
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void) {
 

+ 0 - 1
iOSClient/Settings/CCManageAccount.m

@@ -22,7 +22,6 @@
 //
 
 #import "CCManageAccount.h"
-#import "NSNotificationCenter+MainThread.h"
 #import "NCBridgeSwift.h"
 #import "CCUtility.h"
 

+ 0 - 1
iOSClient/Settings/NCSettings.m

@@ -26,7 +26,6 @@
 #import "CCManageAccount.h"
 #import "CCManageAutoUpload.h"
 #import "NCBridgeSwift.h"
-#import "NSNotificationCenter+MainThread.h"
 #import <LocalAuthentication/LocalAuthentication.h>
 #import <TOPasscodeViewController/TOPasscodeViewController.h>
 

+ 0 - 1
iOSClient/Shares/NCShares.swift

@@ -34,7 +34,6 @@ class NCShares: NCCollectionViewCommon {
         titleCurrentFolder = NSLocalizedString("_list_shares_", comment: "")
         layoutKey = NCGlobal.shared.layoutViewShares
         enableSearchBar = false
-        headerMenuButtonsView = true
         headerRichWorkspaceDisable = true
         emptyImage = UIImage(named: "share")?.image(color: .gray, size: UIScreen.main.bounds.width)
         emptyTitle = "_list_shares_no_files_"

BIN
iOSClient/Supporting Files/af.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/an.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ar.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ast.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/az.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/be.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/br.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bs.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ca.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/da.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/de.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/el.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/en-GB.lproj/Localizable.strings


+ 22 - 7
iOSClient/Supporting Files/en.lproj/Localizable.strings

@@ -134,8 +134,7 @@
 "_of_"                      = "of";
 "_internal_modify_"         = "Edit with internal editor";
 "_database_corrupt_"        = "Oops something went wrong, please enter your credentials but don't worry, your files have remained secure";
-"_download_locally_"        = "Download locally";
-"_livephoto_save_"          = "Save Live Photo to photo album";
+"_livephoto_save_"          = "Save Live Photo to Photo Album";
 "_livephoto_save_error_"    = "Error during save of Live Photo";
 "_file_conflict_num_"       = "file conflict";
 "_file_conflicts_num_"      = "file conflicts";
@@ -159,12 +158,15 @@
 
 /* MARK: Files lock */
 
+"_lock_"                            = "Lock";
+"_unlock_"                          = "Unlock";
 "_lock_file_"                       = "Lock file";
 "_unlock_file_"                     = "Unlock file";
 "_lock_selected_files_"             = "Lock files";
 "_unlock_selected_files_"           = "Unlock files";
 "_locked_by_"                       = "Locked by %@";
 "_file_locked_no_override_"         = "This file is locked. It cannot be overridden.";
+"_lock_no_permissions_selected_"    = "Not allowed for some selected files or folders";
 
 /* Remove a file from a list, don't delete it entirely */
 "_remove_file_"             = "Remove file";
@@ -487,6 +489,9 @@
 "_file_saved_local_"            = "File saved on local storage.";
 "_file_not_present_"            = "Error: File not present, please reload.";
 "_order_by_"                    = "Sort by";
+"_name_"                        = "Name";
+"_date_"                        = "Date";
+"_size_"                        = "Size";
 "_order_by_name_a_z_"           = "Sort by name (from A to Z)";
 "_sorted_by_name_a_z_"          = "Sorted by name (from A to Z)";
 "_order_by_name_z_a_"           = "Sort by name (from Z to A)";
@@ -518,7 +523,7 @@
 "_reload_"                      = "Reload";
 "_open_in_"                     = "Open in …";
 "_open_"                        = "Open …";
-"_remove_local_file_"           = "Remove from cache";
+"_remove_local_file_"           = "Remove locally";
 "_add_local_"                   = "Add to local storage";
 "_comm_erro_pull_down_"         = "Attention: Communication error with the server. Pull down to refresh.";
 "_file_not_downloaded_"         = "file not downloaded";
@@ -537,6 +542,7 @@
 "_offline_folder_confirm_"      = "After enabling the offline folder, all files in it will be synchronized with the server, continue?";
 "_file_not_found_reload_"       = "File not found, pull down to refresh";
 "_title_section_download_"      = "DOWNLOAD";
+"_download_"                    = "Download";
 "_title_section_upload_"        = "UPLOAD";
 "_group_alphabetic_yes_"        = "✓ Group alphabetically";
 "_group_alphabetic_no_"         = "Group alphabetically";
@@ -589,7 +595,7 @@
 "_status_in_upload_"            = "In upload";
 "_status_uploading_"            = "Uploading";
 "_status_upload_error_"         = "Error, waiting for upload";
-"_select_media_folder_"         = "Select the \"Media\" folder";
+"_select_media_folder_"         = "Set Media folder";
 "_media_viewimage_show_"        = "Show only images";
 "_media_viewvideo_show_"        = "Show only video";
 "_media_show_all_"              = "Show both";
@@ -694,7 +700,7 @@
 "_notifications_"                   = "Notifications";
 "_no_notification_"                 = "No notifications yet";
 "_autoupload_filename_title_"       = "Auto upload filename";
-"_untitled_txt_"                    = "Untitled.txt";
+"_untitled_"                        = "Untitled";
 "_text_upload_title_"               = "Upload text file";
 "_e2e_settings_title_"              = "Encryption";
 "_e2e_settings_"                    = "End-to-end encryption";
@@ -751,8 +757,10 @@
 "_trash_delete_all_description_"    = "Do you want to empty the trash bin?";
 "_trash_no_trash_"                  = "No files deleted";
 "_trash_no_trash_description_"      = "You can restore deleted files from here";
-"_trash_restore_selected_"          = "Restore selected files";
-"_trash_delete_selected_"           = "Delete selected files";
+"_trash_restore_selected_"          = "Restore selected files?";
+"_trash_delete_selected_"           = "Delete selected files?";
+"_recover_"                         = "Recover";
+"_confirm_delete_selected_"         = "Are you sure you want to delete the selected items?";
 "_manage_file_offline_"             = "Manage offline files";
 "_set_available_offline_"           = "Set as available offline";
 "_remove_available_offline_"        = "Remove as available offline";
@@ -911,6 +919,7 @@
 "_status_e2ee_on_server_"   = "The end-to-end encryption is already configured in the server but not yet enabled on this device";
 "_status_e2ee_not_setup_"   = "The end-to-end encryption is not yet configured on the server";
 "_status_e2ee_configured_"  = "The end-to-end encryption is correctly configured";
+"_e2ee_set_as_offline_"     = "Not allowed for encrypted files or folders";
 "_change_upload_filename_"  = "Do you want to save the file with a different name?";
 "_save_file_as_"            = "Save file as %@";
 "_password_ascii_"          = "The password cannot contain special characters";
@@ -954,6 +963,10 @@
 "_zoom_"                    = "Zoom";
 "_zoom_in_"                 = "Zoom in";
 "_zoom_out_"                = "Zoom out";
+"_select_photos_"           = "Select photos";
+"_selected_photo_"          = "selected photo";
+"_selected_photos_"         = "selected photos";
+"_delete_selected_photos_"  = "Delete selected photos";
 
 // Video
 "_select_trace_"            = "Select the trace";
@@ -998,6 +1011,8 @@
 "_off_"                         = "Off";
 "_grid_view_"                   = "Show grid view";
 "_list_view_"                   = "Show list view";
+"_list_"                        = "List";
+"_icons_"                       = "Icons";
 
 // MARK: Plan customer
 "_leave_plan_title"             = "We're sorry to see you go";

BIN
iOSClient/Supporting Files/eo.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-419.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-AR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CL.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-DO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-EC.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-GT.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-HN.lproj/Localizable.strings


Some files were not shown because too many files changed in this diff