Browse Source

Version 5.2.5 (#2869)

Marino Faggiana 1 year ago
parent
commit
654487c5a6
26 changed files with 238 additions and 129 deletions
  1. 15 0
      File Provider Extension UI/DocumentActionViewController.swift
  2. 10 2
      Nextcloud.xcodeproj/project.pbxproj
  3. 6 2
      iOSClient/AppDelegate.swift
  4. 3 30
      iOSClient/Data/NCManageDatabase+Metadata.swift
  5. 1 1
      iOSClient/Main/Collection Common/NCCollectionViewCommon.swift
  6. 2 2
      iOSClient/Main/Collection Common/NCCollectionViewCommonSelectTabBar.swift
  7. 2 2
      iOSClient/Main/Main.storyboard
  8. 1 1
      iOSClient/Main/NCActionCenter.swift
  9. 5 17
      iOSClient/Main/NCMainTabBar.swift
  10. 35 0
      iOSClient/Main/NCMainTabBarController.swift
  11. 3 3
      iOSClient/NCGlobal.swift
  12. 46 51
      iOSClient/Networking/NCNetworkingProcess.swift
  13. 1 1
      iOSClient/Networking/NCService.swift
  14. BIN
      iOSClient/Supporting Files/ast.lproj/Localizable.strings
  15. BIN
      iOSClient/Supporting Files/da.lproj/Localizable.strings
  16. BIN
      iOSClient/Supporting Files/es.lproj/Localizable.strings
  17. BIN
      iOSClient/Supporting Files/fr.lproj/Localizable.strings
  18. BIN
      iOSClient/Supporting Files/ja-JP.lproj/Localizable.strings
  19. BIN
      iOSClient/Supporting Files/lb.lproj/Localizable.strings
  20. BIN
      iOSClient/Supporting Files/tr.lproj/Localizable.strings
  21. BIN
      iOSClient/Supporting Files/uk.lproj/Localizable.strings
  22. 4 4
      iOSClient/Transfers/NCTransfers.swift
  23. 5 12
      iOSClient/Utility/NCUtility.swift
  24. 15 0
      iOSClient/Utility/ThreadSafeArray.swift
  25. 67 0
      iOSClient/Viewer/NCViewerMedia/NCViewerMedia+VisionKit.swift
  26. 17 1
      iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift

+ 15 - 0
File Provider Extension UI/DocumentActionViewController.swift

@@ -5,6 +5,21 @@
 //  Created by Marino Faggiana on 30/01/23.
 //  Copyright © 2023 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 FileProviderUI

+ 10 - 2
Nextcloud.xcodeproj/project.pbxproj

@@ -72,6 +72,7 @@
 		F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; };
 		F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; };
 		F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; };
+		F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; };
 		F3131EDD2B038E4A0018DB28 /* XLForm in Frameworks */ = {isa = PBXBuildFile; productRef = F3131EDC2B038E4A0018DB28 /* XLForm */; };
 		F3131EDF2B038F2A0018DB28 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = F3131EDE2B038F2A0018DB28 /* SnapshotTesting */; };
 		F3131EE12B038F560018DB28 /* SnapshotTestingHEIC in Frameworks */ = {isa = PBXBuildFile; productRef = F3131EE02B038F560018DB28 /* SnapshotTestingHEIC */; };
@@ -639,6 +640,7 @@
 		F793E59E28B763C2005E4B02 /* NCAskAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = F733598025C1C188002ABA72 /* NCAskAuthorization.swift */; };
 		F793E59F28B764F6005E4B02 /* NCContentPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F765608E23BF813500765969 /* NCContentPresenter.swift */; };
 		F793E5A128B76541005E4B02 /* NotificationCenter+MainThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70460512499061800BB98A7 /* NotificationCenter+MainThread.swift */; };
+		F794E13D2BBBFF2E003693D7 /* NCMainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F794E13C2BBBFF2E003693D7 /* NCMainTabBarController.swift */; };
 		F798F0E225880608000DAFFD /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70CEF5523E9C7E50007035B /* UIColor+Extension.swift */; };
 		F798F0E725880609000DAFFD /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70CEF5523E9C7E50007035B /* UIColor+Extension.swift */; };
 		F798F0EC2588060A000DAFFD /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70CEF5523E9C7E50007035B /* UIColor+Extension.swift */; };
@@ -977,6 +979,7 @@
 		F30A96042A27299D00D7BCFE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
 		F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = EnvVars.stencil; sourceTree = "<group>"; };
 		F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EnvVars.generated.swift; path = Sourcery/EnvVars.generated.swift; sourceTree = "<group>"; };
+		F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCViewerMedia+VisionKit.swift"; sourceTree = "<group>"; };
 		F3131ED82B038DB20018DB28 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/System/iOSSupport/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
 		F3131EDA2B038DB90018DB28 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/System/iOSSupport/System/Library/Frameworks/WidgetKit.framework; sourceTree = DEVELOPER_DIR; };
 		F31F69422A2F6D4500162F76 /* NextcloudSnapshotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudSnapshotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1316,6 +1319,7 @@
 		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>"; };
+		F794E13C2BBBFF2E003693D7 /* NCMainTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMainTabBarController.swift; sourceTree = "<group>"; };
 		F79918A021997F9000C2E308 /* UICKeyChainStore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UICKeyChainStore.framework; path = Carthage/Build/iOS/UICKeyChainStore.framework; sourceTree = "<group>"; };
 		F79918A72199840500C2E308 /* Sheeeeeeeeet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sheeeeeeeeet.framework; path = Carthage/Build/iOS/Sheeeeeeeeet.framework; sourceTree = "<group>"; };
 		F79A65C22191D90F00FF6DCC /* NCSelect.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCSelect.storyboard; sourceTree = "<group>"; };
@@ -1826,6 +1830,7 @@
 				F7603298252F0E550015A421 /* Collection Common */,
 				F7226EDB1EE4089300EBECB1 /* Main.storyboard */,
 				F7682FDF23C36B0500983A04 /* NCMainTabBar.swift */,
+				F794E13C2BBBFF2E003693D7 /* NCMainTabBarController.swift */,
 				F75B0ABC244C4DBB00E58DCA /* NCActionCenter.swift */,
 				F77444F7222816D5000D5EB0 /* NCPickerViewController.swift */,
 				F737DA9C2B7B893C0063BAFC /* NCPasscode.swift */,
@@ -2234,6 +2239,7 @@
 				F70753F62542A9C000972D44 /* NCViewerMediaPage.storyboard */,
 				F70753EA2542A99800972D44 /* NCViewerMediaPage.swift */,
 				F70753F02542A9A200972D44 /* NCViewerMedia.swift */,
+				F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */,
 				F79EDA9E26B004980007D134 /* NCPlayer */,
 			);
 			path = NCViewerMedia;
@@ -3999,6 +4005,7 @@
 				F785EE9D246196DF00B3F945 /* NCNetworkingE2EE.swift in Sources */,
 				F76673ED22C901F6007ED366 /* FileProviderDomain.swift in Sources */,
 				F7A321AD1E9E6AD50069AD1B /* CCAdvanced.m in Sources */,
+				F794E13D2BBBFF2E003693D7 /* NCMainTabBarController.swift in Sources */,
 				F77B0E4F1D118A16002130FE /* CCManageAutoUpload.m in Sources */,
 				F7CBC1252BAC8B0000EC1D55 /* NCSectionHeaderEmptyData.swift in Sources */,
 				F75C0C4823D1FAE300163CC8 /* NCRichWorkspaceCommon.swift in Sources */,
@@ -4032,6 +4039,7 @@
 				F761856C29E98543006EB3B0 /* NCIntroCollectionViewCell.swift in Sources */,
 				F75DD765290ABB25002EB562 /* Intent.intentdefinition in Sources */,
 				F74B6D952A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */,
+				F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */,
 				F702F2F725EE5CED008F8E80 /* NCLogin.swift in Sources */,
 				F7E98C1627E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */,
 				F7F4F11227ECDC52008676F9 /* UIFont+Extension.swift in Sources */,
@@ -5065,7 +5073,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 5.2.4;
+				MARKETING_VERSION = 5.2.5;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -5127,7 +5135,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 5.2.4;
+				MARKETING_VERSION = 5.2.5;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;

+ 6 - 2
iOSClient/AppDelegate.swift

@@ -362,10 +362,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             let items = await NCAutoUpload.shared.initAutoUpload()
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) auto upload with \(items) uploads")
             let results = await NCNetworkingProcess.shared.start()
-            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) networking process with download: \(results.counterDownload) upload: \(results.counterUpload)")
+            NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) networking process with download: \(results.counterDownloading) upload: \(results.counterUploading)")
 
             if taskText == "ProcessingTask",
-               items == 0, results.counterDownload == 0, results.counterUpload == 0,
+               items == 0, results.counterDownloading == 0, results.counterUploading == 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
@@ -377,6 +377,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
                     NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) end synchronization for \(directory.serverUrl), errorCode: \(results.errorCode), item: \(results.items)")
                 }
             }
+
+            let counter = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND (session == %@ || session == %@) AND status != %d", self.account, NCNetworking.shared.sessionDownloadBackground, NCNetworking.shared.sessionUploadBackground, NCGlobal.shared.metadataStatusNormal))?.count ?? 0
+            UIApplication.shared.applicationIconBadgeNumber = counter
+
             NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) completion handle")
             completion()
         }

+ 3 - 30
iOSClient/Data/NCManageDatabase+Metadata.swift

@@ -488,7 +488,6 @@ extension NCManageDatabase {
 
     @discardableResult
     func addMetadata(_ metadata: tableMetadata) -> tableMetadata? {
-
         let result = tableMetadata.init(value: metadata)
 
         do {
@@ -522,7 +521,6 @@ extension NCManageDatabase {
     }
 
     func addMetadatas(_ metadatas: [tableMetadata]) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -534,7 +532,6 @@ extension NCManageDatabase {
     }
 
     func deleteMetadata(predicate: NSPredicate) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -547,7 +544,6 @@ extension NCManageDatabase {
     }
 
     func deleteMetadata(results: Results<tableMetadata>) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -559,7 +555,6 @@ extension NCManageDatabase {
     }
 
     func moveMetadata(ocId: String, serverUrlTo: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -573,7 +568,6 @@ extension NCManageDatabase {
     }
 
     func renameMetadata(fileNameTo: String, ocId: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -592,7 +586,6 @@ extension NCManageDatabase {
     }
 
     func setMetadataEtagResource(ocId: String, etagResource: String?) {
-
         guard let etagResource = etagResource else { return }
 
         do {
@@ -607,7 +600,6 @@ extension NCManageDatabase {
     }
 
     func setMetadataFavorite(ocId: String, favorite: Bool) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -620,7 +612,6 @@ extension NCManageDatabase {
     }
 
     func setMetadataLivePhotoByServer(account: String, ocId: String, livePhotoFile: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -635,7 +626,6 @@ extension NCManageDatabase {
     }
 
     func updateMetadatasFavorite(account: String, metadatas: [tableMetadata]) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -653,7 +643,6 @@ extension NCManageDatabase {
     }
 
     func setMetadataEncrypted(ocId: String, encrypted: Bool) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -666,7 +655,6 @@ extension NCManageDatabase {
     }
 
     func setMetadataFileNameView(serverUrl: String, fileName: String, newFileNameView: String, account: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -679,7 +667,6 @@ extension NCManageDatabase {
     }
 
     func getMetadata(predicate: NSPredicate) -> tableMetadata? {
-
         do {
             let realm = try Realm()
             realm.refresh()
@@ -693,7 +680,6 @@ extension NCManageDatabase {
     }
 
     func getMetadata(predicate: NSPredicate, sorted: String, ascending: Bool) -> tableMetadata? {
-
         do {
             let realm = try Realm()
             realm.refresh()
@@ -925,7 +911,6 @@ extension NCManageDatabase {
     }
 
     func getTableMetadatasDirectoryFavoriteIdentifierRank(account: String) -> [String: NSNumber] {
-
         var listIdentifierRank: [String: NSNumber] = [:]
         var counter = 10 as Int64
 
@@ -945,7 +930,6 @@ extension NCManageDatabase {
     }
 
     @objc func clearMetadatasUpload(account: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -958,7 +942,6 @@ extension NCManageDatabase {
     }
 
     func readMarkerMetadata(account: String, fileId: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -972,8 +955,7 @@ extension NCManageDatabase {
         }
     }
 
-    func getAssetLocalIdentifiersUploaded(account: String) -> [String] {
-
+    func getAssetLocalIdentifiersUploaded(account: String) -> [String]? {
         var assetLocalIdentifiers: [String] = []
 
         do {
@@ -982,15 +964,15 @@ extension NCManageDatabase {
             for result in results {
                 assetLocalIdentifiers.append(result.assetLocalIdentifier)
             }
+            return assetLocalIdentifiers
         } catch let error as NSError {
             NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
         }
 
-        return assetLocalIdentifiers
+        return nil
     }
 
     func clearAssetLocalIdentifiers(_ assetLocalIdentifiers: [String], account: String) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -1005,7 +987,6 @@ extension NCManageDatabase {
     }
 
     func getMetadataLivePhoto(metadata: tableMetadata) -> tableMetadata? {
-
         guard metadata.isLivePhoto else { return nil }
 
         do {
@@ -1020,7 +1001,6 @@ extension NCManageDatabase {
     }
 
     func isMetadataShareOrMounted(metadata: tableMetadata, metadataFolder: tableMetadata?) -> Bool {
-
         var isShare = false
         var isMounted = false
 
@@ -1043,7 +1023,6 @@ extension NCManageDatabase {
     }
 
     func getMetadataConflict(account: String, serverUrl: String, fileNameView: String) -> tableMetadata? {
-
         // verify exists conflict
         let fileNameExtension = (fileNameView as NSString).pathExtension.lowercased()
         let fileNameNoExtension = (fileNameView as NSString).deletingPathExtension
@@ -1069,7 +1048,6 @@ extension NCManageDatabase {
     }
 
     func getMetadataFromDirectory(account: String, serverUrl: String) -> tableMetadata? {
-
         do {
             let realm = try Realm()
             realm.refresh()
@@ -1084,7 +1062,6 @@ extension NCManageDatabase {
     }
 
     func getMetadatasFromGroupfolders(account: String, urlBase: String, userId: String) -> [tableMetadata] {
-
         var metadatas: [tableMetadata] = []
         let homeServerUrl = utilityFileSystem.getHomeServer(urlBase: urlBase, userId: userId)
 
@@ -1108,7 +1085,6 @@ extension NCManageDatabase {
     }
 
     func getMetadatasInError(account: String) -> Results<tableMetadata>? {
-
         do {
             let realm = try Realm()
             let results = realm.objects(tableMetadata.self).filter("account == %@ AND errorCodeCounter > 1", account)
@@ -1122,7 +1098,6 @@ extension NCManageDatabase {
 
     @discardableResult
     func updateMetadatas(_ metadatas: [tableMetadata], predicate: NSPredicate) -> (metadatasDifferentCount: Int, metadatasModified: Int) {
-
         var metadatasDifferentCount: Int = 0
         var metadatasModified: Int = 0
 
@@ -1153,7 +1128,6 @@ extension NCManageDatabase {
     }
 
     func replaceMetadata(_ metadatas: [tableMetadata], predicate: NSPredicate) {
-
         do {
             let realm = try Realm()
             try realm.write {
@@ -1173,7 +1147,6 @@ extension NCManageDatabase {
     }
 
     func getMediaMetadatas(predicate: NSPredicate) -> ThreadSafeArray<tableMetadata>? {
-
         do {
             let realm = try Realm()
             let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: "date", ascending: false)

+ 1 - 1
iOSClient/Main/Collection Common/NCCollectionViewCommon.swift

@@ -91,7 +91,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        tabBarSelect = NCCollectionViewCommonSelectTabBar(tabBarController: tabBarController, delegate: self)
+        tabBarSelect = NCCollectionViewCommonSelectTabBar(tabBarController: tabBarController as? NCMainTabBarController, delegate: self)
         self.navigationController?.presentationController?.delegate = self
 
         // CollectionView & layout

+ 2 - 2
iOSClient/Main/Collection Common/NCCollectionViewCommonSelectTabBar.swift

@@ -34,7 +34,7 @@ protocol NCCollectionViewCommonSelectTabBarDelegate: AnyObject {
 }
 
 class NCCollectionViewCommonSelectTabBar: ObservableObject {
-    var tabBarController: UITabBarController?
+    var tabBarController: NCMainTabBarController?
     var hostingController: UIViewController?
     open weak var delegate: NCCollectionViewCommonSelectTabBarDelegate?
 
@@ -47,7 +47,7 @@ class NCCollectionViewCommonSelectTabBar: ObservableObject {
     @Published var enableLock = false
     @Published var isSelectedEmpty = true
 
-    init(tabBarController: UITabBarController? = nil, delegate: NCCollectionViewCommonSelectTabBarDelegate? = nil) {
+    init(tabBarController: NCMainTabBarController? = nil, delegate: NCCollectionViewCommonSelectTabBarDelegate? = nil) {
         let rootView = NCCollectionViewCommonSelectTabBarView(tabBarSelect: self)
         hostingController = UIHostingController(rootView: rootView)
 

+ 2 - 2
iOSClient/Main/Main.storyboard

@@ -17,10 +17,10 @@
             </objects>
             <point key="canvasLocation" x="7770" y="899"/>
         </scene>
-        <!--Tab Bar Controller-->
+        <!--Main Tab Bar Controller-->
         <scene sceneID="gY3-Ur-rTC">
             <objects>
-                <tabBarController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="FkP-Lh-8zt" sceneMemberID="viewController">
+                <tabBarController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="FkP-Lh-8zt" customClass="NCMainTabBarController" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
                     <toolbarItems/>
                     <navigationItem key="navigationItem" id="ozb-fg-0GE">
                         <barButtonItem key="backBarButtonItem" title="Back" id="oUu-2v-gUF"/>

+ 1 - 1
iOSClient/Main/NCActionCenter.swift

@@ -575,7 +575,7 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec
             }
             mostViewController.navigationController?.popToRootViewController(animated: false)
 
-            if let tabBarController = appDelegate.window?.rootViewController as? UITabBarController {
+            if let tabBarController = appDelegate.window?.rootViewController as? NCMainTabBarController {
                 tabBarController.selectedIndex = 0
                 if let navigationController = tabBarController.viewControllers?.first as? UINavigationController {
                     navigationController.popToRootViewController(animated: false)

+ 5 - 17
iOSClient/Main/NCMainTabBar.swift

@@ -211,32 +211,20 @@ class NCMainTabBar: UITabBar {
 
     func updateBadgeNumberUI(counterDownload: Int, counterUpload: Int) {
 
-        UIApplication.shared.applicationIconBadgeNumber = counterUpload
+        UIApplication.shared.applicationIconBadgeNumber = counterDownload + counterUpload
 
         if let item = self.items?[0] {
             if counterDownload == 0, counterUpload == 0 {
                 item.badgeValue = nil
             } else if counterDownload > 0, counterUpload == 0 {
-                var badgeValue = String("↓ \(counterDownload)")
-                if counterDownload >= NCBrandOptions.shared.maxConcurrentOperationDownload {
-                    badgeValue = String("↓ \(NCBrandOptions.shared.maxConcurrentOperationDownload)+")
-                }
+                let badgeValue = String("↓ \(counterDownload)")
                 item.badgeValue = badgeValue
             } else if counterDownload == 0, counterUpload > 0 {
-                var badgeValue = String("↑ \(counterUpload)")
-                if counterUpload >= NCBrandOptions.shared.maxConcurrentOperationUpload {
-                    badgeValue = String("↑ \(NCBrandOptions.shared.maxConcurrentOperationUpload)+")
-                }
+                let badgeValue = String("↑ \(counterUpload)")
                 item.badgeValue = badgeValue
             } else {
-                var badgeValueDownload = String("↓ \(counterDownload)")
-                if counterDownload >= NCBrandOptions.shared.maxConcurrentOperationDownload {
-                    badgeValueDownload = String("↓ \(NCBrandOptions.shared.maxConcurrentOperationDownload)+")
-                }
-                var badgeValueUpload = String("↑ \(counterUpload)")
-                if counterUpload >= NCBrandOptions.shared.maxConcurrentOperationUpload {
-                    badgeValueUpload = String("↑ \(NCBrandOptions.shared.maxConcurrentOperationUpload)+")
-                }
+                let badgeValueDownload = String("↓ \(counterDownload)")
+                let badgeValueUpload = String("↑ \(counterUpload)")
                 item.badgeValue = badgeValueDownload + " " + badgeValueUpload
             }
         }

+ 35 - 0
iOSClient/Main/NCMainTabBarController.swift

@@ -0,0 +1,35 @@
+//
+//  NCMainTabBarController.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 02/04/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
+
+class NCMainTabBarController: UITabBarController {
+
+    var identifier: String = UUID().uuidString
+
+    // MARK: - Life Cycle
+
+    required init?(coder: NSCoder) {
+        super.init(coder: coder)
+    }
+}

+ 3 - 3
iOSClient/NCGlobal.swift

@@ -464,9 +464,9 @@ class NCGlobal: NSObject {
     @objc var capabilityE2EEApiVersion: String                  = ""
 
     var capabilityRichdocumentsEnabled: Bool                    = false
-    var capabilityRichdocumentsMimetypes: [String]              = []
-    var capabilityActivity: [String]                            = []
-    var capabilityNotification: [String]                        = []
+    var capabilityRichdocumentsMimetypes = ThreadSafeArray<String>()
+    var capabilityActivity = ThreadSafeArray<String>()
+    var capabilityNotification = ThreadSafeArray<String>()
 
     var capabilityFilesUndelete: Bool                           = false
     var capabilityFilesLockVersion: String                      = ""    // NC 24

+ 46 - 51
iOSClient/Networking/NCNetworkingProcess.swift

@@ -44,14 +44,33 @@ class NCNetworkingProcess: NSObject {
     func startTimer() {
         self.timerProcess?.invalidate()
         self.timerProcess = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
-            guard !self.appDelegate.account.isEmpty, !self.pauseProcess else { return }
+            guard !self.appDelegate.account.isEmpty,
+                  !self.pauseProcess else { return }
+
             if NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status != %d", self.appDelegate.account, NCGlobal.shared.metadataStatusNormal)).isEmpty {
+                //
+                // Remove Photo CameraRoll
+                //
+                if NCKeychain().removePhotoCameraRoll,
+                   UIApplication.shared.applicationState == .active,
+                   let localIdentifiers = NCManageDatabase.shared.getAssetLocalIdentifiersUploaded(account: self.appDelegate.account),
+                   !localIdentifiers.isEmpty {
+                    self.pauseProcess = true
+                    PHPhotoLibrary.shared().performChanges({
+                        PHAssetChangeRequest.deleteAssets(PHAsset.fetchAssets(withLocalIdentifiers: localIdentifiers, options: nil) as NSFastEnumeration)
+                    }, completionHandler: { _, _ in
+                        NCManageDatabase.shared.clearAssetLocalIdentifiers(localIdentifiers, account: self.appDelegate.account)
+                        self.pauseProcess = false
+                    })
+                }
                 NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber), object: nil, userInfo: ["counterDownload": 0, "counterUpload": 0])
             } else {
                 Task {
                     let results = await self.start()
-                    print("[INFO] PROCESS Download: \(results.counterDownload) Upload: \(results.counterUpload)")
-                    NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber), object: nil, userInfo: ["counterDownload": results.counterDownload, "counterUpload": results.counterUpload])
+                    let counterDownload = await NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND session == %@ AND (status == %d || status == %d)", self.appDelegate.account, NCNetworking.shared.sessionDownloadBackground, NCGlobal.shared.metadataStatusWaitDownload, NCGlobal.shared.metadataStatusDownloading))?.count ?? 0
+                    let counterUpload = await NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND session == %@ AND (status == %d || status == %d)", self.appDelegate.account, NCNetworking.shared.sessionUploadBackground, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploading))?.count ?? 0
+                    print("[INFO] PROCESS Download: \(results.counterDownloading)/\(counterDownload) Upload: \(results.counterUploading)/\(counterUpload)")
+                    NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterUpdateBadgeNumber), object: nil, userInfo: ["counterDownload": counterDownload, "counterUpload": counterUpload])
                 }
             }
         })
@@ -62,7 +81,7 @@ class NCNetworkingProcess: NSObject {
     }
 
     @discardableResult
-    func start() async -> (counterDownload: Int, counterUpload: Int) {
+    func start() async -> (counterDownloading: Int, counterUploading: Int) {
         self.pauseProcess = true
         let applicationState = await UIApplication.shared.applicationState
         let maxConcurrentOperationDownload = NCBrandOptions.shared.maxConcurrentOperationDownload
@@ -71,25 +90,25 @@ class NCNetworkingProcess: NSObject {
         let sessionUploadSelectors = [NCGlobal.shared.selectorUploadFileNODelete, NCGlobal.shared.selectorUploadFile, NCGlobal.shared.selectorUploadAutoUpload, NCGlobal.shared.selectorUploadAutoUploadAll]
         let metadatasDownloading = await NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusDownloading))
         let metadatasUploading = await NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploading))
-        let metadatasUploadInError: [tableMetadata] = await NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploadError), sorted: "sessionDate", ascending: true) ?? []
+        let metadatasUploadError: [tableMetadata] = await NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND status == %d", self.appDelegate.account, NCGlobal.shared.metadataStatusUploadError), sorted: "sessionDate", ascending: true) ?? []
         let isWiFi = NCNetworking.shared.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi
-        var counterDownload = metadatasDownloading.count
-        var counterUpload = metadatasUploading.count
+        var counterDownloading = metadatasDownloading.count
+        var counterUploading = metadatasUploading.count
         if applicationState == .active {
             self.hud = await JGProgressHUD()
         }
 
         // ------------------------ DOWNLOAD
 
-        let limitDownload = maxConcurrentOperationDownload - counterDownload
-        let metadatasDownload = await 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 {
-            counterDownload += 1
+        let limitDownload = maxConcurrentOperationDownload - counterDownloading
+        let metadatasWaitDownload = await 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 metadatasWaitDownload where counterDownloading < maxConcurrentOperationDownload {
+            counterDownloading += 1
             NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
         }
-        if counterDownload == 0 {
-            let metadatasDownloadInError: [tableMetadata] = await 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 {
+        if counterDownloading == 0 {
+            let metadatasDownloadError: [tableMetadata] = await 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 metadatasDownloadError {
                 NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
                                                            sessionError: "",
                                                            status: NCGlobal.shared.metadataStatusWaitDownload)
@@ -102,14 +121,14 @@ class NCNetworkingProcess: NSObject {
         for metadata in metadatasUploading.unique(map: { $0.serverUrl }) {
             if metadata.isDirectoryE2EE {
                 self.pauseProcess = false
-                return (counterDownload, counterUpload)
+                return (counterDownloading, counterUploading)
             }
         }
 
         // CHUNK - only one for time
         if !metadatasUploading.filter({ $0.chunk > 0 }).isEmpty {
             self.pauseProcess = false
-            return (counterDownload, counterUpload)
+            return (counterDownloading, counterUploading)
         }
 
         // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
@@ -122,13 +141,13 @@ class NCNetworkingProcess: NSObject {
             filesNameLocalPath.append(task.description)
         }
 
-        for sessionSelector in sessionUploadSelectors where counterUpload < maxConcurrentOperationUpload {
-            let limitUpload = maxConcurrentOperationUpload - counterUpload
-            let metadatasUpload = await 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 sessionSelector in sessionUploadSelectors where counterUploading < maxConcurrentOperationUpload {
+            let limitUpload = maxConcurrentOperationUpload - counterUploading
+            let metadatasWaitUpload = await 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 !metadatasWaitUpload.isEmpty {
+                NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] PROCESS (UPLOAD) find \(metadatasWaitUpload.count) items")
             }
-            for metadata in metadatasUpload where counterUpload < maxConcurrentOperationUpload {
+            for metadata in metadatasWaitUpload where counterUploading < maxConcurrentOperationUpload {
                 // Is already in upload background? skipped
                 let fileNameLocalPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
                 if filesNameLocalPath.contains(fileNameLocalPath) {
@@ -143,7 +162,7 @@ class NCNetworkingProcess: NSObject {
                 if metadatas.isEmpty {
                     NCManageDatabase.shared.deleteMetadata(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))
                 }
-                for metadata in metadatas where counterUpload < maxConcurrentOperationUpload {
+                for metadata in metadatas where counterUploading < maxConcurrentOperationUpload {
                     // isE2EE
                     let isInDirectoryE2EE = metadata.isDirectoryE2EE
                     // NO WiFi
@@ -154,15 +173,15 @@ class NCNetworkingProcess: NSObject {
                         if isInDirectoryE2EE || metadata.chunk > 0 {
                             maxConcurrentOperationUpload = 1
                         }
-                        counterUpload += 1
+                        counterUploading += 1
                     }
                 }
             }
         }
 
         // No upload available ? --> Retry Upload in Error
-        if counterUpload == 0 {
-            for metadata in metadatasUploadInError {
+        if counterUploading == 0 {
+            for metadata in metadatasUploadError {
                 // Verify QUOTA
                 if metadata.sessionError.contains("\(NCGlobal.shared.errorQuota)") {
                     NextcloudKit.shared.getUserProfile { _, userProfile, _, error in
@@ -182,32 +201,8 @@ class NCNetworkingProcess: NSObject {
             }
         }
 
-        // No upload available ? --> Delete Assets
-        if NCKeychain().removePhotoCameraRoll,
-           applicationState == .active,
-           counterUpload == 0,
-           metadatasUploadInError.isEmpty {
-            await self.deleteAssetsLocalIdentifiers(account: self.appDelegate.account)
-        }
-
         self.pauseProcess = false
-        return (counterDownload, counterUpload)
-    }
-
-    @MainActor private func deleteAssetsLocalIdentifiers(account: String) async {
-        guard !NCPasscode.shared.isPasscodePresented,
-              NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND session CONTAINS[cd] %@", account, "upload")).isEmpty else {
-            return
-        }
-        let localIdentifiers = NCManageDatabase.shared.getAssetLocalIdentifiersUploaded(account: account)
-        if localIdentifiers.isEmpty { return }
-        let assets = PHAsset.fetchAssets(withLocalIdentifiers: localIdentifiers, options: nil)
-
-        try? await PHPhotoLibrary.shared().performChanges {
-            PHAssetChangeRequest.deleteAssets(assets as NSFastEnumeration)
-            NCManageDatabase.shared.clearAssetLocalIdentifiers(localIdentifiers, account: account)
-        }
-        return
+        return (counterDownloading, counterUploading)
     }
 
     // MARK: -

+ 1 - 1
iOSClient/Networking/NCService.swift

@@ -227,7 +227,7 @@ class NCService: NSObject {
             }
 
             // Added UTI for Collabora
-            for mimeType in NCGlobal.shared.capabilityRichdocumentsMimetypes {
+            NCGlobal.shared.capabilityRichdocumentsMimetypes.forEach { mimeType in
                 NextcloudKit.shared.nkCommonInstance.addInternalTypeIdentifier(typeIdentifier: mimeType, classFile: NKCommon.TypeClassFile.document.rawValue, editor: NCGlobal.shared.editorCollabora, iconName: NKCommon.TypeIconFile.document.rawValue, name: "document")
             }
 

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


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


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


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


BIN
iOSClient/Supporting Files/ja-JP.lproj/Localizable.strings


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


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


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


+ 4 - 4
iOSClient/Transfers/NCTransfers.swift

@@ -55,6 +55,9 @@ class NCTransfers: NCCollectionViewCommon, NCTransferCellDelegate {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         reloadDataSource()
+        Task {
+            await NCNetworkingProcess.shared.verifyZombie()
+        }
     }
 
     override func setNavigationLeftItems() {
@@ -256,10 +259,7 @@ class NCTransfers: NCCollectionViewCommon, NCTransferCellDelegate {
     }
 
     override func reloadDataSource(withQueryDB: Bool = true) {
-        Task {
-            await NCNetworkingProcess.shared.verifyZombie()
-            super.reloadDataSource(withQueryDB: withQueryDB)
-        }
+        super.reloadDataSource(withQueryDB: withQueryDB)
     }
 
     override func reloadDataSourceNetwork() {

+ 5 - 12
iOSClient/Utility/NCUtility.swift

@@ -48,28 +48,21 @@ class NCUtility: NSObject {
     }
 
     func isRichDocument(_ metadata: tableMetadata) -> Bool {
-
         guard let mimeType = CCUtility.getMimeType(metadata.fileNameView) else {
-            return false
+            return true
         }
 
         // contentype
-        for richdocumentMimetype: String in NCGlobal.shared.capabilityRichdocumentsMimetypes {
-            if richdocumentMimetype.contains(metadata.contentType) || metadata.contentType == "text/plain" {
-                return true
-            }
+        if !NCGlobal.shared.capabilityRichdocumentsMimetypes.filter({ $0.contains(metadata.contentType) || $0.contains("text/plain") }).isEmpty {
+            return true
         }
 
         // mimetype
         if !NCGlobal.shared.capabilityRichdocumentsMimetypes.isEmpty && mimeType.components(separatedBy: ".").count > 2 {
-
             let mimeTypeArray = mimeType.components(separatedBy: ".")
             let mimeType = mimeTypeArray[mimeTypeArray.count - 2] + "." + mimeTypeArray[mimeTypeArray.count - 1]
-
-            for richdocumentMimetype: String in NCGlobal.shared.capabilityRichdocumentsMimetypes {
-                if richdocumentMimetype.contains(mimeType) {
-                    return true
-                }
+            if !NCGlobal.shared.capabilityRichdocumentsMimetypes.filter({ $0.contains(mimeType) }).isEmpty {
+                return true
             }
         }
 

+ 15 - 0
iOSClient/Utility/ThreadSafeArray.swift

@@ -6,6 +6,21 @@
 //  Copyright © 2024 Marino Faggiana. All rights reserved.
 //
 //  http://basememara.com/creating-thread-safe-arrays-in-swift/
+//  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
 

+ 67 - 0
iOSClient/Viewer/NCViewerMedia/NCViewerMedia+VisionKit.swift

@@ -0,0 +1,67 @@
+//
+//  NCViewerMedia+VisionKit.swift
+//  Nextcloud
+//
+//  Created by Milen on 18.03.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 VisionKit
+
+extension NCViewerMedia {
+    @available(iOS 16.0, *)
+    func analyzeCurrentImage() {
+        if let image = image {
+            Task {
+                let configuration = ImageAnalyzer.Configuration([.text, .machineReadableCode, .visualLookUp])
+                    let analysis = try? await analyzer.imageAnalyzer?.analyze(image, configuration: configuration)
+                    if image == self.image {
+                        analyzer.imageInteraction?.analysis = analysis
+                        analyzer.imageInteraction?.preferredInteractionTypes = .automatic
+                    }
+            }
+        }
+    }
+}
+
+// TODO: Remove when min SDK is 16
+@MainActor
+struct Analyzer {
+    private var _imageAnalyzer: Any?
+    private var _imageInteraction: Any?
+
+    @available(iOS 16, *)
+    var imageAnalyzer: ImageAnalyzer? {
+        get { return _imageAnalyzer as? ImageAnalyzer }
+        set { _imageAnalyzer = newValue }
+    }
+
+    @available(iOS 16, *)
+    var imageInteraction: ImageAnalysisInteraction? {
+        get { return _imageInteraction as? ImageAnalysisInteraction }
+        set { _imageInteraction = newValue }
+    }
+
+    init() {
+        if #available(iOS 16, *) {
+            imageAnalyzer = ImageAnalyzer()
+            imageInteraction = ImageAnalysisInteraction()
+        }
+    }
+}

+ 17 - 1
iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift

@@ -29,6 +29,7 @@ import SwiftUI
 import MobileVLCKit
 import JGProgressHUD
 import Alamofire
+import VisionKit
 
 public protocol NCViewerMediaViewDelegate: AnyObject {
     func didOpenDetail()
@@ -48,12 +49,21 @@ class NCViewerMedia: UIViewController {
     private var tipView: EasyTipView?
     private let player = VLCMediaPlayer()
     private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+    let analyzer = Analyzer()
     let utilityFileSystem = NCUtilityFileSystem()
     let utility = NCUtility()
     weak var viewerMediaPage: NCViewerMediaPage?
     var playerToolBar: NCPlayerToolBar?
     var ncplayer: NCPlayer?
-    var image: UIImage?
+    var image: UIImage? {
+        didSet {
+            if #available(iOS 16, *), metadata.isImage {
+                analyzer.imageInteraction?.preferredInteractionTypes = []
+                analyzer.imageInteraction?.analysis = nil
+//                analyzeCurrentImage()
+            }
+        }
+    }
     var metadata: tableMetadata = tableMetadata()
     var index: Int = 0
     var doubleTapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
@@ -134,6 +144,12 @@ class NCViewerMedia: UIViewController {
         self.imageVideoContainer.image = nil
 
         loadImage()
+
+        if #available(iOS 16, *) {
+            if let interaction = analyzer.imageInteraction {
+                self.imageVideoContainer.addInteraction(interaction)
+            }
+        }
     }
 
     override func viewWillAppear(_ animated: Bool) {