浏览代码

Merge pull request #1898 from nextcloud/improvedScan

Improved scan documents
Marino Faggiana 3 年之前
父节点
当前提交
829be4887b

+ 10 - 4
Nextcloud.xcodeproj/project.pbxproj

@@ -193,7 +193,7 @@
 		F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = F758A01127A7F03E0069468B /* JGProgressHUD */; };
 		F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = F758A01127A7F03E0069468B /* JGProgressHUD */; };
 		F758B45A212C564000515F55 /* NCScan.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F758B457212C564000515F55 /* NCScan.storyboard */; };
 		F758B45A212C564000515F55 /* NCScan.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F758B457212C564000515F55 /* NCScan.storyboard */; };
 		F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45D212C569C00515F55 /* NCScanCell.swift */; };
 		F758B45E212C569D00515F55 /* NCScanCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45D212C569C00515F55 /* NCScanCell.swift */; };
-		F758B460212C56A400515F55 /* NCScanCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45F212C56A400515F55 /* NCScanCollectionView.swift */; };
+		F758B460212C56A400515F55 /* NCScan.swift in Sources */ = {isa = PBXBuildFile; fileRef = F758B45F212C56A400515F55 /* NCScan.swift */; };
 		F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; };
 		F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; };
 		F75A9EE723796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; };
 		F75A9EE723796C6F0044CFCE /* NCNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75A9EE523796C6F0044CFCE /* NCNetworking.swift */; };
 		F75AC2431F1F62450073EC19 /* NCManageAutoUploadFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75AC2421F1F62450073EC19 /* NCManageAutoUploadFileName.swift */; };
 		F75AC2431F1F62450073EC19 /* NCManageAutoUploadFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75AC2421F1F62450073EC19 /* NCManageAutoUploadFileName.swift */; };
@@ -326,6 +326,7 @@
 		F7AE00F5230D5F9E007ACF8A /* NCLoginWeb.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AE00F4230D5F9E007ACF8A /* NCLoginWeb.swift */; };
 		F7AE00F5230D5F9E007ACF8A /* NCLoginWeb.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AE00F4230D5F9E007ACF8A /* NCLoginWeb.swift */; };
 		F7AE00F8230E81CB007ACF8A /* NCBrowserWeb.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AE00F7230E81CB007ACF8A /* NCBrowserWeb.swift */; };
 		F7AE00F8230E81CB007ACF8A /* NCBrowserWeb.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7AE00F7230E81CB007ACF8A /* NCBrowserWeb.swift */; };
 		F7AE00FA230E81EB007ACF8A /* NCBrowserWeb.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7AE00F9230E81EB007ACF8A /* NCBrowserWeb.storyboard */; };
 		F7AE00FA230E81EB007ACF8A /* NCBrowserWeb.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7AE00F9230E81EB007ACF8A /* NCBrowserWeb.storyboard */; };
+		F7B6B70427C4E7FA00A7F6EB /* NCScan+CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B6B70327C4E7FA00A7F6EB /* NCScan+CollectionView.swift */; };
 		F7B7504B2397D38F004E13EC /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B7504A2397D38E004E13EC /* UIImage+Extensions.swift */; };
 		F7B7504B2397D38F004E13EC /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B7504A2397D38E004E13EC /* UIImage+Extensions.swift */; };
 		F7B8B83025681C3400967775 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F7B8B82F25681C3400967775 /* GoogleService-Info.plist */; };
 		F7B8B83025681C3400967775 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F7B8B82F25681C3400967775 /* GoogleService-Info.plist */; };
 		F7B8CD91261AF3F7007C1359 /* NCNetworkingChunkedUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B8CD90261AF3F7007C1359 /* NCNetworkingChunkedUpload.swift */; };
 		F7B8CD91261AF3F7007C1359 /* NCNetworkingChunkedUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B8CD90261AF3F7007C1359 /* NCNetworkingChunkedUpload.swift */; };
@@ -653,7 +654,7 @@
 		F7581D2325EFDDDF004DC699 /* NCMedia+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCMedia+Menu.swift"; sourceTree = "<group>"; };
 		F7581D2325EFDDDF004DC699 /* NCMedia+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCMedia+Menu.swift"; sourceTree = "<group>"; };
 		F758B457212C564000515F55 /* NCScan.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCScan.storyboard; sourceTree = "<group>"; };
 		F758B457212C564000515F55 /* NCScan.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCScan.storyboard; sourceTree = "<group>"; };
 		F758B45D212C569C00515F55 /* NCScanCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScanCell.swift; sourceTree = "<group>"; };
 		F758B45D212C569C00515F55 /* NCScanCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScanCell.swift; sourceTree = "<group>"; };
-		F758B45F212C56A400515F55 /* NCScanCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScanCollectionView.swift; sourceTree = "<group>"; };
+		F758B45F212C56A400515F55 /* NCScan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCScan.swift; sourceTree = "<group>"; };
 		F75A9EE523796C6F0044CFCE /* NCNetworking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCNetworking.swift; sourceTree = "<group>"; };
 		F75A9EE523796C6F0044CFCE /* NCNetworking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCNetworking.swift; sourceTree = "<group>"; };
 		F75AC2421F1F62450073EC19 /* NCManageAutoUploadFileName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCManageAutoUploadFileName.swift; sourceTree = "<group>"; };
 		F75AC2421F1F62450073EC19 /* NCManageAutoUploadFileName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCManageAutoUploadFileName.swift; sourceTree = "<group>"; };
 		F75B0ABC244C4DBB00E58DCA /* NCFunctionCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFunctionCenter.swift; sourceTree = "<group>"; };
 		F75B0ABC244C4DBB00E58DCA /* NCFunctionCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFunctionCenter.swift; sourceTree = "<group>"; };
@@ -783,6 +784,7 @@
 		F7AF7632246BEDFE00B86E3C /* TOPasscodeViewController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TOPasscodeViewController.framework; path = Carthage/Build/iOS/TOPasscodeViewController.framework; sourceTree = "<group>"; };
 		F7AF7632246BEDFE00B86E3C /* TOPasscodeViewController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TOPasscodeViewController.framework; path = Carthage/Build/iOS/TOPasscodeViewController.framework; sourceTree = "<group>"; };
 		F7B1076C25D3CF2800E72DE2 /* BackgroundTasks.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BackgroundTasks.framework; path = System/Library/Frameworks/BackgroundTasks.framework; sourceTree = SDKROOT; };
 		F7B1076C25D3CF2800E72DE2 /* BackgroundTasks.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BackgroundTasks.framework; path = System/Library/Frameworks/BackgroundTasks.framework; sourceTree = SDKROOT; };
 		F7B1A7761EBB3C8000BFB6D1 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
 		F7B1A7761EBB3C8000BFB6D1 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
+		F7B6B70327C4E7FA00A7F6EB /* NCScan+CollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCScan+CollectionView.swift"; sourceTree = "<group>"; };
 		F7B7504A2397D38E004E13EC /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = "<group>"; };
 		F7B7504A2397D38E004E13EC /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = "<group>"; };
 		F7B8B82F25681C3400967775 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; };
 		F7B8B82F25681C3400967775 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; };
 		F7B8CD90261AF3F7007C1359 /* NCNetworkingChunkedUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingChunkedUpload.swift; sourceTree = "<group>"; };
 		F7B8CD90261AF3F7007C1359 /* NCNetworkingChunkedUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingChunkedUpload.swift; sourceTree = "<group>"; };
@@ -1184,7 +1186,8 @@
 			children = (
 			children = (
 				F758B457212C564000515F55 /* NCScan.storyboard */,
 				F758B457212C564000515F55 /* NCScan.storyboard */,
 				F758B45D212C569C00515F55 /* NCScanCell.swift */,
 				F758B45D212C569C00515F55 /* NCScanCell.swift */,
-				F758B45F212C56A400515F55 /* NCScanCollectionView.swift */,
+				F758B45F212C56A400515F55 /* NCScan.swift */,
+				F7B6B70327C4E7FA00A7F6EB /* NCScan+CollectionView.swift */,
 			);
 			);
 			path = ScanDocument;
 			path = ScanDocument;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -2397,7 +2400,7 @@
 				F716B75F26F09DF600D37EFC /* NCKTVHTTPCache.swift in Sources */,
 				F716B75F26F09DF600D37EFC /* NCKTVHTTPCache.swift in Sources */,
 				AF36077127BFA4E8001A243D /* ParallelWorker.swift in Sources */,
 				AF36077127BFA4E8001A243D /* ParallelWorker.swift in Sources */,
 				F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */,
 				F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */,
-				F758B460212C56A400515F55 /* NCScanCollectionView.swift in Sources */,
+				F758B460212C56A400515F55 /* NCScan.swift in Sources */,
 				F78ACD52219046DC0088454D /* NCSectionHeaderFooter.swift in Sources */,
 				F78ACD52219046DC0088454D /* NCSectionHeaderFooter.swift in Sources */,
 				F749C10C23C4A5340027D966 /* NCIntroViewController.swift in Sources */,
 				F749C10C23C4A5340027D966 /* NCIntroViewController.swift in Sources */,
 				F710D2022405826100A6033D /* NCViewer+Menu.swift in Sources */,
 				F710D2022405826100A6033D /* NCViewer+Menu.swift in Sources */,
@@ -2438,6 +2441,7 @@
 				3781B9B023DB2B7E006B4B1D /* AppDelegate+Menu.swift in Sources */,
 				3781B9B023DB2B7E006B4B1D /* AppDelegate+Menu.swift in Sources */,
 				F73D5E47246DE09200DF6467 /* NCElementsJSON.swift in Sources */,
 				F73D5E47246DE09200DF6467 /* NCElementsJSON.swift in Sources */,
 				F710D1F52405770F00A6033D /* NCViewerPDF.swift in Sources */,
 				F710D1F52405770F00A6033D /* NCViewerPDF.swift in Sources */,
+				F7B6B70427C4E7FA00A7F6EB /* NCScan+CollectionView.swift in Sources */,
 				F7501C332212E57500FB1415 /* NCMedia.swift in Sources */,
 				F7501C332212E57500FB1415 /* NCMedia.swift in Sources */,
 				F70BFC7420E0FA7D00C67599 /* NCUtility.swift in Sources */,
 				F70BFC7420E0FA7D00C67599 /* NCUtility.swift in Sources */,
 				F79EDAA526B004980007D134 /* NCPlayer.swift in Sources */,
 				F79EDAA526B004980007D134 /* NCPlayer.swift in Sources */,
@@ -2763,6 +2767,7 @@
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
 				CURRENT_PROJECT_VERSION = 2;
 				CURRENT_PROJECT_VERSION = 2;
 				DEVELOPMENT_TEAM = 6JLRKY9ZV7;
 				DEVELOPMENT_TEAM = 6JLRKY9ZV7;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
@@ -2821,6 +2826,7 @@
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
 				CURRENT_PROJECT_VERSION = 2;
 				CURRENT_PROJECT_VERSION = 2;
 				DEVELOPMENT_TEAM = 6JLRKY9ZV7;
 				DEVELOPMENT_TEAM = 6JLRKY9ZV7;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;

+ 2 - 2
iOSClient/EmptyView/NCEmptyView.xib

@@ -22,13 +22,13 @@
                         <constraint firstAttribute="width" constant="150" id="g0C-P6-l3d"/>
                         <constraint firstAttribute="width" constant="150" id="g0C-P6-l3d"/>
                     </constraints>
                     </constraints>
                 </imageView>
                 </imageView>
-                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="crs-DO-owR">
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="crs-DO-owR">
                     <rect key="frame" x="20" y="180" width="310" height="24"/>
                     <rect key="frame" x="20" y="180" width="310" height="24"/>
                     <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
                     <fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
                     <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <nil key="highlightedColor"/>
                     <nil key="highlightedColor"/>
                 </label>
                 </label>
-                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D4p-sI-mNB">
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D4p-sI-mNB">
                     <rect key="frame" x="20" y="224" width="310" height="17"/>
                     <rect key="frame" x="20" y="224" width="310" height="17"/>
                     <constraints>
                     <constraints>
                         <constraint firstAttribute="height" relation="lessThanOrEqual" constant="50" id="u7B-jW-bWI"/>
                         <constraint firstAttribute="height" relation="lessThanOrEqual" constant="50" id="u7B-jW-bWI"/>

+ 25 - 0
iOSClient/Extensions/UIImage+Extensions.swift

@@ -204,4 +204,29 @@ extension UIImage {
         // Return the downsampled image as UIImage
         // Return the downsampled image as UIImage
         return UIImage(cgImage: downsampledImage)
         return UIImage(cgImage: downsampledImage)
     }
     }
+    
+    // Source:
+    // https://stackoverflow.com/questions/27092354/rotating-uiimage-in-swift/47402811#47402811
+    
+    func rotate(radians: Float) -> UIImage? {
+        var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
+        // Trim off the extremely small float value to prevent core graphics from rounding it up
+        newSize.width = floor(newSize.width)
+        newSize.height = floor(newSize.height)
+
+        UIGraphicsBeginImageContextWithOptions(newSize, true, self.scale)
+        let context = UIGraphicsGetCurrentContext()!
+
+        // Move origin to middle
+        context.translateBy(x: newSize.width / 2, y: newSize.height / 2)
+        // Rotate around middle
+        context.rotate(by: CGFloat(radians))
+        // Draw the image at its center
+        self.draw(in: CGRect(x: -self.size.width / 2, y: -self.size.height / 2, width: self.size.width, height: self.size.height))
+
+        let newImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        return newImage
+    }
 }
 }

+ 2 - 2
iOSClient/Main/Create cloud/NCCreateFormUploadScanDocument.swift

@@ -762,8 +762,8 @@ class NCCreateScanDocument: NSObject, VNDocumentCameraViewControllerDelegate {
         }
         }
 
 
         controller.dismiss(animated: true) {
         controller.dismiss(animated: true) {
-            if self.viewController is NCScanCollectionView {
-                (self.viewController as! NCScanCollectionView).loadImage()
+            if let viewController = self.viewController as? NCScan {
+                viewController.loadImage()
             } else {
             } else {
                 let storyboard = UIStoryboard(name: "NCScan", bundle: nil)
                 let storyboard = UIStoryboard(name: "NCScan", bundle: nil)
                 let controller = storyboard.instantiateInitialViewController()!
                 let controller = storyboard.instantiateInitialViewController()!

+ 226 - 0
iOSClient/ScanDocument/NCScan+CollectionView.swift

@@ -0,0 +1,226 @@
+//
+//  NCScan+CollectionView.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 22/02/22.
+//  Copyright © 2022 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
+
+@available(iOS 13.0, *)
+extension NCScan: UICollectionViewDataSource {
+
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+
+        return collectionView == collectionViewSource ? itemsSource.count : imagesDestination.count
+    }
+
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+        if collectionView == collectionViewSource {
+
+            let cell = (collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as? NCScanCell)!
+
+            let fileNamePath = CCUtility.getDirectoryScan() + "/" + itemsSource[indexPath.row]
+
+            guard let data = try? Data(contentsOf: URL(fileURLWithPath: fileNamePath)), var image = UIImage(data: data) else { return cell }
+
+            let imageWidthInPixels = image.size.width * image.scale
+            let imageHeightInPixels = image.size.height * image.scale
+
+            // 72 DPI
+            if imageWidthInPixels > 595 || imageHeightInPixels > 842 {
+                image = image.resizeImage(size: CGSize(width: 595, height: 842), isAspectRation: true) ?? image
+            }
+
+            cell.customImageView?.image = image
+            cell.delete.action(for: .touchUpInside) { sender in
+
+                let buttonPosition: CGPoint = (sender as? UIButton)!.convert(.zero, to: self.collectionViewSource)
+                if let indexPath = self.collectionViewSource.indexPathForItem(at: buttonPosition) {
+
+                    let fileNameAtPath = CCUtility.getDirectoryScan() + "/" + self.itemsSource[indexPath.row]
+                    CCUtility.removeFile(atPath: fileNameAtPath)
+                    self.itemsSource.remove(at: indexPath.row)
+
+                    self.collectionViewSource.deleteItems(at: [indexPath])
+                }
+            }
+
+            return cell
+
+        } else {
+
+            let cell = (collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as? NCScanCell)!
+            cell.delegate = self
+            cell.index = indexPath.row
+
+            var image = imagesDestination[indexPath.row]
+
+            let imageWidthInPixels = image.size.width * image.scale
+            let imageHeightInPixels = image.size.height * image.scale
+
+            // 72 DPI
+            if imageWidthInPixels > 595 || imageHeightInPixels > 842 {
+                image = image.resizeImage(size: CGSize(width: 595, height: 842), isAspectRation: true) ?? image
+            }
+
+            cell.customImageView?.image = filter(image: image)
+            cell.customLabel.text = NSLocalizedString("_scan_document_pdf_page_", comment: "") + " " + "\(indexPath.row + 1)"
+
+            return cell
+        }
+    }
+}
+
+@available(iOS 13.0, *)
+extension NCScan: UICollectionViewDragDelegate {
+    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
+
+        if collectionView == collectionViewSource {
+            let item = itemsSource[indexPath.row]
+            let itemProvider = NSItemProvider(object: item as NSString)
+            let dragItem = UIDragItem(itemProvider: itemProvider)
+
+            dragItem.localObject = item
+
+            return [dragItem]
+
+        } else {
+            let item = imagesDestination[indexPath.row]
+            let itemProvider = NSItemProvider(object: item as UIImage)
+            let dragItem = UIDragItem(itemProvider: itemProvider)
+
+            dragItem.localObject = item
+
+            return [dragItem]
+        }
+    }
+
+    func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
+
+        if collectionView == collectionViewSource {
+            let item = itemsSource[indexPath.row]
+            let itemProvider = NSItemProvider(object: item as NSString)
+            let dragItem = UIDragItem(itemProvider: itemProvider)
+
+            dragItem.localObject = item
+
+            return [dragItem]
+
+        } else {
+            let item = imagesDestination[indexPath.row]
+            let itemProvider = NSItemProvider(object: item as UIImage)
+            let dragItem = UIDragItem(itemProvider: itemProvider)
+
+            dragItem.localObject = item
+
+            return [dragItem]
+        }
+    }
+
+    func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters? {
+
+        let previewParameters = UIDragPreviewParameters()
+        if collectionView == collectionViewSource {
+            previewParameters.visiblePath = UIBezierPath(rect: CGRect(x: 20, y: 20, width: 100, height: 100))
+        } else {
+            previewParameters.visiblePath = UIBezierPath(rect: CGRect(x: 20, y: 20, width: 80, height: 80))
+        }
+
+        return previewParameters
+    }
+}
+
+@available(iOS 13.0, *)
+extension NCScan: UICollectionViewDropDelegate {
+
+    func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
+
+        return true // session.canLoadObjects(ofClass: NSString.self)
+    }
+
+    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
+
+        if collectionView == collectionViewSource {
+
+            if collectionView.hasActiveDrag {
+                return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
+            } else {
+                return UICollectionViewDropProposal(operation: .forbidden)
+            }
+
+        } else {
+
+            if collectionView.hasActiveDrag {
+                return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
+            } else {
+                return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
+            }
+        }
+    }
+
+    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
+
+        let destinationIndexPath: IndexPath
+
+        switch coordinator.proposal.operation {
+
+        case .move:
+
+            if let indexPath = coordinator.destinationIndexPath {
+
+                destinationIndexPath = indexPath
+
+            } else {
+
+                // Get last index path of table view.
+                let section = collectionView.numberOfSections - 1
+                let row = collectionView.numberOfItems(inSection: section)
+
+                destinationIndexPath = IndexPath(row: row, section: section)
+            }
+            reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
+
+        case .copy:
+
+            // Get last index path of table view.
+            let section = collectionView.numberOfSections - 1
+            let row = collectionView.numberOfItems(inSection: section)
+
+            destinationIndexPath = IndexPath(row: row, section: section)
+            copyItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
+
+        default:
+            return
+        }
+    }
+
+    func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) {
+
+        collectionViewDestination.reloadData()
+
+        // Save button
+        if imagesDestination.isEmpty {
+            save.isEnabled = false
+        } else {
+            save.isEnabled = true
+        }
+    }
+}

+ 8 - 2
iOSClient/ScanDocument/NCScan.storyboard

@@ -8,10 +8,10 @@
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     </dependencies>
     <scenes>
     <scenes>
-        <!--Scan Collection View-->
+        <!--Scan-->
         <scene sceneID="tne-QT-ifu">
         <scene sceneID="tne-QT-ifu">
             <objects>
             <objects>
-                <viewController extendedLayoutIncludesOpaqueBars="YES" id="BYZ-38-t0r" customClass="NCScanCollectionView" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                <viewController extendedLayoutIncludesOpaqueBars="YES" id="BYZ-38-t0r" customClass="NCScan" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                     <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -105,6 +105,9 @@
                                                         <constraint firstAttribute="height" constant="23" id="QOj-Nj-nAA"/>
                                                         <constraint firstAttribute="height" constant="23" id="QOj-Nj-nAA"/>
                                                     </constraints>
                                                     </constraints>
                                                     <state key="normal" image="deleteScan"/>
                                                     <state key="normal" image="deleteScan"/>
+                                                    <connections>
+                                                        <action selector="touchUpInsideDelete:" destination="Pph-tY-PGX" eventType="touchUpInside" id="KcT-WM-s1K"/>
+                                                    </connections>
                                                 </button>
                                                 </button>
                                                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XEo-o0-dSF" userLabel="Rotate">
                                                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XEo-o0-dSF" userLabel="Rotate">
                                                     <rect key="frame" x="98" y="0.0" width="22" height="22"/>
                                                     <rect key="frame" x="98" y="0.0" width="22" height="22"/>
@@ -113,6 +116,9 @@
                                                         <constraint firstAttribute="height" constant="22" id="fd5-QY-wlr"/>
                                                         <constraint firstAttribute="height" constant="22" id="fd5-QY-wlr"/>
                                                     </constraints>
                                                     </constraints>
                                                     <state key="normal" image="rotate"/>
                                                     <state key="normal" image="rotate"/>
+                                                    <connections>
+                                                        <action selector="touchUpInsideRotate:" destination="Pph-tY-PGX" eventType="touchUpInside" id="x5z-go-m4Y"/>
+                                                    </connections>
                                                 </button>
                                                 </button>
                                             </subviews>
                                             </subviews>
                                         </view>
                                         </view>

+ 372 - 0
iOSClient/ScanDocument/NCScan.swift

@@ -0,0 +1,372 @@
+//
+//  NCScan.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 21/08/18.
+//  Copyright (c) 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 UIKit
+
+@available(iOS 13.0, *)
+class NCScan: UIViewController, NCScanCellCellDelegate {
+
+    @IBOutlet weak var collectionViewSource: UICollectionView!
+    @IBOutlet weak var collectionViewDestination: UICollectionView!
+    @IBOutlet weak var cancel: UIBarButtonItem!
+    @IBOutlet weak var save: UIBarButtonItem!
+    @IBOutlet weak var add: UIButton!
+    @IBOutlet weak var transferDown: UIButton!
+    @IBOutlet weak var labelTitlePDFzone: UILabel!
+    @IBOutlet weak var segmentControlFilter: UISegmentedControl!
+
+    // Data Source for collectionViewSource
+    internal var itemsSource: [String] = []
+
+    // Data Source for collectionViewDestination
+    internal var imagesDestination: [UIImage] = []
+    internal var itemsDestination: [String] = []
+
+    internal let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
+
+    enum TypeFilter {
+        case original
+        case grayScale
+        case bn
+    }
+    internal var filter: TypeFilter = TypeFilter.original
+
+    // MARK: - View Life Cycle
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        view.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground
+        navigationItem.title = NSLocalizedString("_scanned_images_", comment: "")
+
+        collectionViewSource.dragInteractionEnabled = true
+        collectionViewSource.dragDelegate = self
+        collectionViewSource.dropDelegate = self
+        collectionViewSource.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground
+
+        collectionViewDestination.dragInteractionEnabled = true
+        collectionViewDestination.dropDelegate = self
+        collectionViewDestination.dragDelegate = self
+        collectionViewDestination.reorderingCadence = .fast // default value - .immediate
+        collectionViewDestination.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground
+
+        cancel.title = NSLocalizedString("_cancel_", comment: "")
+        save.title = NSLocalizedString("_save_", comment: "")
+
+        labelTitlePDFzone.text = NSLocalizedString("_scan_label_document_zone_", comment: "")
+        labelTitlePDFzone.backgroundColor = NCBrandColor.shared.systemGray6
+        labelTitlePDFzone.textColor = NCBrandColor.shared.label
+
+        segmentControlFilter.setTitle(NSLocalizedString("_filter_original_", comment: ""), forSegmentAt: 0)
+        segmentControlFilter.setTitle(NSLocalizedString("_filter_grayscale_", comment: ""), forSegmentAt: 1)
+        segmentControlFilter.setTitle(NSLocalizedString("_filter_bn_", comment: ""), forSegmentAt: 2)
+
+        add.setImage(UIImage(named: "plus")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
+        transferDown.setImage(UIImage(named: "transferDown")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
+
+        let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(recognizer:)))
+        collectionViewSource.addGestureRecognizer(longPressRecognizer)
+        let longPressRecognizerPlus = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(recognizer:)))
+        add.addGestureRecognizer(longPressRecognizerPlus)
+
+        collectionViewSource.reloadData()
+        collectionViewDestination.reloadData()
+
+        loadImage()
+    }
+
+    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+        super.traitCollectionDidChange(previousTraitCollection)
+
+        add.setImage(UIImage(named: "plus")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
+        transferDown.setImage(UIImage(named: "transferDown")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
+    }
+
+    override var canBecomeFirstResponder: Bool { return true }
+
+    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+        if action == #selector(pasteImage) {
+            return true
+        }
+        return false
+    }
+
+    @IBAction func cancelAction(sender: UIBarButtonItem) {
+        self.dismiss(animated: true, completion: nil)
+    }
+
+    @IBAction func saveAction(sender: UIBarButtonItem) {
+
+        if !imagesDestination.isEmpty {
+
+            var images: [UIImage] = []
+            let serverUrl = appDelegate.activeServerUrl
+
+            for image in imagesDestination {
+                images.append(filter(image: image)!)
+            }
+
+            let formViewController = NCCreateFormUploadScanDocument(serverUrl: serverUrl, arrayImages: images)
+            self.navigationController?.pushViewController(formViewController, animated: true)
+        }
+    }
+
+    @IBAction func add(sender: UIButton) {
+
+        NCCreateScanDocument.shared.openScannerDocument(viewController: self)
+    }
+
+    @IBAction func transferDown(sender: UIButton) {
+
+        for fileName in itemsSource {
+
+            if !itemsDestination.contains(fileName) {
+
+                let fileNamePathAt = CCUtility.getDirectoryScan() + "/" + fileName
+
+                guard let data = try? Data(contentsOf: URL(fileURLWithPath: fileNamePathAt)), let image = UIImage(data: data) else { return }
+
+                imagesDestination.append(image)
+                itemsDestination.append(fileName)
+            }
+        }
+
+        // Save button
+        if imagesDestination.isEmpty {
+            save.isEnabled = false
+        } else {
+            save.isEnabled = true
+        }
+
+        collectionViewDestination.reloadData()
+    }
+
+    @IBAction func indexChanged(_ sender: AnyObject) {
+
+        switch segmentControlFilter.selectedSegmentIndex {
+        case 0:
+            filter = .original
+        case 1:
+            filter = .grayScale
+        case 2:
+            filter = .bn
+        default:
+            break
+        }
+
+        collectionViewDestination.reloadData()
+    }
+
+    func loadImage() {
+
+        itemsSource.removeAll()
+
+        do {
+            let atPath = CCUtility.getDirectoryScan()!
+            let directoryContents = try FileManager.default.contentsOfDirectory(atPath: atPath)
+            for fileName in directoryContents where fileName.first != "." {
+                itemsSource.append(fileName)
+            }
+        } catch {
+            print(error.localizedDescription)
+        }
+
+        itemsSource = itemsSource.sorted()
+
+        collectionViewSource.reloadData()
+
+        // Save button
+        if imagesDestination.isEmpty {
+            save.isEnabled = false
+        } else {
+            save.isEnabled = true
+        }
+    }
+
+    func filter(image: UIImage) -> UIImage? {
+
+        var inputContrast: Double = 0
+
+        if filter == .original {
+            return image
+        }
+
+        if filter == .grayScale {
+            inputContrast = 1
+        }
+
+        if filter == .bn {
+            inputContrast = 4
+        }
+
+        let ciImage = CIImage(image: image)!
+        let imageFilter = ciImage.applyingFilter("CIColorControls", parameters: ["inputSaturation": 0, "inputContrast": inputContrast])
+
+        let context: CIContext = CIContext(options: nil)
+        let cgImage: CGImage = context.createCGImage(imageFilter, from: imageFilter.extent)!
+        let image: UIImage = UIImage(cgImage: cgImage)
+        return image
+    }
+
+    // destinationIndexPath: indexpath of the collection view where the user drops the element
+    // collectionView: collectionView in which reordering needs to be done.
+
+    func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
+
+        let items = coordinator.items
+
+        if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath {
+
+            var dIndexPath = destinationIndexPath
+
+            if dIndexPath.row >= collectionView.numberOfItems(inSection: 0) {
+                dIndexPath.row = collectionView.numberOfItems(inSection: 0) - 1
+            }
+
+            collectionView.performBatchUpdates({
+
+                if collectionView === collectionViewDestination {
+
+                    imagesDestination.remove(at: sourceIndexPath.row)
+                    imagesDestination.insert((item.dragItem.localObject as? UIImage)!, at: dIndexPath.row)
+
+                    let fileName = itemsDestination[sourceIndexPath.row]
+                    itemsDestination.remove(at: sourceIndexPath.row)
+                    itemsDestination.insert(fileName, at: dIndexPath.row)
+
+                } else {
+
+                    itemsSource.remove(at: sourceIndexPath.row)
+                    itemsSource.insert((item.dragItem.localObject as? String)!, at: dIndexPath.row)
+                }
+
+                collectionView.deleteItems(at: [sourceIndexPath])
+                collectionView.insertItems(at: [dIndexPath])
+            })
+
+            coordinator.drop(items.first!.dragItem, toItemAt: dIndexPath)
+        }
+    }
+
+    func copyItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
+        collectionView.performBatchUpdates({
+
+            var indexPaths: [IndexPath] = []
+
+            for (index, item) in coordinator.items.enumerated() {
+
+                let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
+
+                if collectionView === collectionViewDestination {
+
+                    let fileName = (item.dragItem.localObject as? String)!
+                    let fileNamePathAt = CCUtility.getDirectoryScan() + "/" + fileName
+
+                    guard let data = try? Data(contentsOf: URL(fileURLWithPath: fileNamePathAt)), let image = UIImage(data: data) else { return }
+
+                    imagesDestination.insert(image, at: indexPath.row)
+                    itemsDestination.insert(fileName, at: indexPath.row)
+
+                } else {
+
+                    // NOT PERMITTED
+                    return
+                }
+
+                indexPaths.append(indexPath)
+            }
+
+            collectionView.insertItems(at: indexPaths)
+        })
+    }
+
+    @objc func handleLongPressGesture(recognizer: UIGestureRecognizer) {
+
+        if recognizer.state == UIGestureRecognizer.State.began {
+
+            self.becomeFirstResponder()
+
+            let pasteboard = UIPasteboard.general
+
+            if let recognizerView = recognizer.view, let recognizerSuperView = recognizerView.superview, pasteboard.hasImages {
+
+                UIMenuController.shared.menuItems = [UIMenuItem(title: "Paste", action: #selector(pasteImage))]
+                UIMenuController.shared.setTargetRect(recognizerView.frame, in: recognizerSuperView)
+                UIMenuController.shared.setMenuVisible(true, animated: true)
+            }
+        }
+    }
+
+    @objc func pasteImage() {
+
+        let pasteboard = UIPasteboard.general
+
+        if pasteboard.hasImages {
+
+            guard let image = pasteboard.image?.fixedOrientation() else { return }
+
+            let fileName = CCUtility.createFileName("scan.png", fileDate: Date(),
+                                                    fileType: PHAssetMediaType.image,
+                                                    keyFileName: NCGlobal.shared.keyFileNameMask,
+                                                    keyFileNameType: NCGlobal.shared.keyFileNameType,
+                                                    keyFileNameOriginal: NCGlobal.shared.keyFileNameOriginal,
+                                                    forcedNewFileName: true)!
+            let fileNamePath = CCUtility.getDirectoryScan() + "/" + fileName
+
+            do {
+                try image.pngData()?.write(to: NSURL.fileURL(withPath: fileNamePath), options: .atomic)
+            } catch {
+                return
+            }
+
+            loadImage()
+        }
+    }
+
+    func delete(with imageIndex: Int, sender: Any) {
+
+        imagesDestination.remove(at: imageIndex)
+        itemsDestination.remove(at: imageIndex)
+
+        // Save button
+        if imagesDestination.isEmpty {
+            save.isEnabled = false
+        } else {
+            save.isEnabled = true
+        }
+
+        collectionViewDestination.reloadData()
+    }
+
+    func rotate(with imageIndex: Int, sender: Any) {
+
+        let indexPath = IndexPath(row: imageIndex, section: 0)
+        if let cell = collectionViewDestination.cellForItem(at: indexPath) as? NCScanCell {
+
+            var image = imagesDestination[imageIndex]
+            image = image.rotate(radians: .pi / 2)!
+            imagesDestination[imageIndex] = image
+            cell.customImageView.image = image
+        }
+    }
+}

+ 16 - 0
iOSClient/ScanDocument/NCScanCell.swift

@@ -29,4 +29,20 @@ class NCScanCell: UICollectionViewCell {
     @IBOutlet weak var customLabel: UILabel!
     @IBOutlet weak var customLabel: UILabel!
     @IBOutlet weak var delete: UIButton!
     @IBOutlet weak var delete: UIButton!
     @IBOutlet weak var rotate: UIButton!
     @IBOutlet weak var rotate: UIButton!
+
+    weak var delegate: NCScanCellCellDelegate?
+    var index = 0
+
+    @IBAction func touchUpInsideDelete(_ sender: Any) {
+        delegate?.delete(with: index, sender: sender)
+    }
+
+    @IBAction func touchUpInsideRotate(_ sender: Any) {
+        delegate?.rotate(with: index, sender: sender)
+    }
+}
+
+protocol NCScanCellCellDelegate: AnyObject {
+    func delete(with index: Int, sender: Any)
+    func rotate(with index: Int, sender: Any)
 }
 }

+ 0 - 649
iOSClient/ScanDocument/NCScanCollectionView.swift

@@ -1,649 +0,0 @@
-//
-//  NCScanCollectionView.swift
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 21/08/18.
-//  Copyright (c) 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 UIKit
-
-@available(iOS 13.0, *)
-class NCScanCollectionView: UIViewController {
-
-    // Data Source for collectionViewSource
-    private var itemsSource: [String] = []
-
-    // Data Source for collectionViewDestination
-    private var imagesDestination: [UIImage] = []
-    private var itemsDestination: [String] = []
-
-    private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
-
-    // MARK: Outlets
-    @IBOutlet weak var collectionViewSource: UICollectionView!
-    @IBOutlet weak var collectionViewDestination: UICollectionView!
-    @IBOutlet weak var cancel: UIBarButtonItem!
-    @IBOutlet weak var save: UIBarButtonItem!
-    @IBOutlet weak var add: UIButton!
-    @IBOutlet weak var transferDown: UIButton!
-    @IBOutlet weak var labelTitlePDFzone: UILabel!
-    @IBOutlet weak var segmentControlFilter: UISegmentedControl!
-
-    // filter
-    enum TypeFilter {
-        case original
-        case grayScale
-        case bn
-    }
-    private var filter: TypeFilter = TypeFilter.original
-
-    override var canBecomeFirstResponder: Bool { return true }
-
-    // MARK: - View Life Cycle
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        view.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground
-        navigationItem.title = NSLocalizedString("_scanned_images_", comment: "")
-
-        collectionViewSource.dragInteractionEnabled = true
-        collectionViewSource.dragDelegate = self
-        collectionViewSource.dropDelegate = self
-        collectionViewSource.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground
-
-        collectionViewDestination.dragInteractionEnabled = true
-        collectionViewDestination.dropDelegate = self
-        collectionViewDestination.dragDelegate = self
-        collectionViewDestination.reorderingCadence = .fast // default value - .immediate
-        collectionViewDestination.backgroundColor = NCBrandColor.shared.secondarySystemGroupedBackground
-
-        cancel.title = NSLocalizedString("_cancel_", comment: "")
-        save.title = NSLocalizedString("_save_", comment: "")
-
-        labelTitlePDFzone.text = NSLocalizedString("_scan_label_document_zone_", comment: "")
-        labelTitlePDFzone.backgroundColor = NCBrandColor.shared.systemGray6
-        labelTitlePDFzone.textColor = NCBrandColor.shared.label
-
-        segmentControlFilter.setTitle(NSLocalizedString("_filter_original_", comment: ""), forSegmentAt: 0)
-        segmentControlFilter.setTitle(NSLocalizedString("_filter_grayscale_", comment: ""), forSegmentAt: 1)
-        segmentControlFilter.setTitle(NSLocalizedString("_filter_bn_", comment: ""), forSegmentAt: 2)
-
-        add.setImage(UIImage(named: "plus")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
-        transferDown.setImage(UIImage(named: "transferDown")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
-
-        let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(recognizer:)))
-        collectionViewSource.addGestureRecognizer(longPressRecognizer)
-        let longPressRecognizerPlus = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(recognizer:)))
-        add.addGestureRecognizer(longPressRecognizerPlus)
-
-        collectionViewSource.reloadData()
-        collectionViewDestination.reloadData()
-
-        loadImage()
-    }
-
-    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
-        super.traitCollectionDidChange(previousTraitCollection)
-
-        add.setImage(UIImage(named: "plus")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
-        transferDown.setImage(UIImage(named: "transferDown")?.image(color: NCBrandColor.shared.label, size: 25), for: .normal)
-    }
-
-    // MARK: Button Action
-
-    @IBAction func cancelAction(sender: UIBarButtonItem) {
-        
-        self.dismiss(animated: true, completion: nil)
-    }
-
-    @IBAction func saveAction(sender: UIBarButtonItem) {
-
-        if imagesDestination.count > 0 {
-
-            var images: [UIImage] = []
-            let serverUrl = appDelegate.activeServerUrl
-
-            for image in imagesDestination {
-                images.append(filter(image: image)!)
-            }
-
-//            if let directory = CCUtility.getDirectoryScanDocuments() {
-//                serverUrl = directory
-//            }
-
-            let formViewController = NCCreateFormUploadScanDocument(serverUrl: serverUrl, arrayImages: images)
-            self.navigationController?.pushViewController(formViewController, animated: true)
-        }
-    }
-
-    @IBAction func add(sender: UIButton) {
-
-        NCCreateScanDocument.shared.openScannerDocument(viewController: self)
-    }
-
-    @IBAction func transferDown(sender: UIButton) {
-
-        for fileName in itemsSource {
-
-            if !itemsDestination.contains(fileName) {
-
-                let fileNamePathAt = CCUtility.getDirectoryScan() + "/" + fileName
-
-                guard let data = try? Data(contentsOf: URL(fileURLWithPath: fileNamePathAt)) else { return }
-                guard let image = UIImage(data: data) else { return }
-
-                imagesDestination.append(image)
-                itemsDestination.append(fileName)
-            }
-        }
-
-        // Save button
-        if imagesDestination.count == 0 {
-            save.isEnabled = false
-        } else {
-            save.isEnabled = true
-        }
-
-        collectionViewDestination.reloadData()
-    }
-
-    @IBAction func indexChanged(_ sender: AnyObject) {
-
-        switch segmentControlFilter.selectedSegmentIndex {
-        case 0:
-            filter = .original
-        case 1:
-            filter = .grayScale
-        case 2:
-            filter = .bn
-        default:
-            break
-        }
-
-        collectionViewDestination.reloadData()
-    }
-
-    func loadImage() {
-
-        itemsSource.removeAll()
-
-        do {
-            let atPath = CCUtility.getDirectoryScan()!
-            let directoryContents = try FileManager.default.contentsOfDirectory(atPath: atPath)
-            for fileName in directoryContents {
-                if fileName.first != "." {
-                    itemsSource.append(fileName)
-                }
-            }
-        } catch {
-            print(error.localizedDescription)
-        }
-
-        itemsSource = itemsSource.sorted()
-
-        collectionViewSource.reloadData()
-
-        // Save button
-        if imagesDestination.count == 0 {
-            save.isEnabled = false
-        } else {
-            save.isEnabled = true
-        }
-    }
-
-    // MARK: Private Methods
-
-    func filter(image: UIImage) -> UIImage? {
-
-        var inputContrast: Double = 0
-
-        if filter == .original {
-            return image
-        }
-
-        if filter == .grayScale {
-            inputContrast = 1
-        }
-
-        if filter == .bn {
-            inputContrast = 4
-        }
-
-        let ciImage = CIImage(image: image)!
-        let imageFilter = ciImage.applyingFilter("CIColorControls", parameters: ["inputSaturation": 0, "inputContrast": inputContrast])
-
-        let context: CIContext = CIContext(options: nil)
-        let cgImage: CGImage = context.createCGImage(imageFilter, from: imageFilter.extent)!
-        let image: UIImage = UIImage(cgImage: cgImage)
-        return image
-    }
-
-    /// This method moves a cell from source indexPath to destination indexPath within the same collection view. It works for only 1 item. If multiple items selected, no reordering happens.
-    ///
-    /// - Parameters:
-    ///   - coordinator: coordinator obtained from performDropWith: UICollectionViewDropDelegate method
-    ///   - destinationIndexPath: indexpath of the collection view where the user drops the element
-    ///   - collectionView: collectionView in which reordering needs to be done.
-
-    private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
-
-        let items = coordinator.items
-
-        if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath {
-
-            var dIndexPath = destinationIndexPath
-
-            if dIndexPath.row >= collectionView.numberOfItems(inSection: 0) {
-                dIndexPath.row = collectionView.numberOfItems(inSection: 0) - 1
-            }
-
-            collectionView.performBatchUpdates({
-
-                if collectionView === collectionViewDestination {
-
-                    imagesDestination.remove(at: sourceIndexPath.row)
-                    imagesDestination.insert((item.dragItem.localObject as? UIImage)!, at: dIndexPath.row)
-
-                    let fileName = itemsDestination[sourceIndexPath.row]
-                    itemsDestination.remove(at: sourceIndexPath.row)
-                    itemsDestination.insert(fileName, at: dIndexPath.row)
-
-                } else {
-
-                    itemsSource.remove(at: sourceIndexPath.row)
-                    itemsSource.insert((item.dragItem.localObject as? String)!, at: dIndexPath.row)
-                }
-
-                collectionView.deleteItems(at: [sourceIndexPath])
-                collectionView.insertItems(at: [dIndexPath])
-            })
-
-            coordinator.drop(items.first!.dragItem, toItemAt: dIndexPath)
-        }
-    }
-
-    /// This method copies a cell from source indexPath in 1st collection view to destination indexPath in 2nd collection view. It works for multiple items.
-    ///
-    /// - Parameters:
-    ///   - coordinator: coordinator obtained from performDropWith: UICollectionViewDropDelegate method
-    ///   - destinationIndexPath: indexpath of the collection view where the user drops the element
-    ///   - collectionView: collectionView in which reordering needs to be done.
-
-    private func copyItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
-        collectionView.performBatchUpdates({
-
-            var indexPaths: [IndexPath] = []
-
-            for (index, item) in coordinator.items.enumerated() {
-
-                let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
-
-                if collectionView === collectionViewDestination {
-
-                    let fileName = (item.dragItem.localObject as? String)!
-                    let fileNamePathAt = CCUtility.getDirectoryScan() + "/" + fileName
-
-                    guard let data = try? Data(contentsOf: URL(fileURLWithPath: fileNamePathAt)) else {
-                        return
-                    }
-                    guard let image =  UIImage(data: data) else {
-                        return
-                    }
-
-                    imagesDestination.insert(image, at: indexPath.row)
-                    itemsDestination.insert(fileName, at: indexPath.row)
-
-                } else {
-
-                    // NOT PERMITTED
-                    return
-                }
-
-                indexPaths.append(indexPath)
-            }
-
-            collectionView.insertItems(at: indexPaths)
-        })
-    }
-
-    // MARK: - UIGestureRecognizerv - Paste
-
-    @objc func handleLongPressGesture(recognizer: UIGestureRecognizer) {
-
-        if recognizer.state == UIGestureRecognizer.State.began {
-
-            self.becomeFirstResponder()
-
-            let pasteboard = UIPasteboard.general
-
-            if let recognizerView = recognizer.view, let recognizerSuperView = recognizerView.superview, pasteboard.hasImages {
-
-                UIMenuController.shared.menuItems = [UIMenuItem(title: "Paste", action: #selector(pasteImage))]
-                UIMenuController.shared.setTargetRect(recognizerView.frame, in: recognizerSuperView)
-                UIMenuController.shared.setMenuVisible(true, animated: true)
-            }
-        }
-    }
-
-    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
-        if action == #selector(pasteImage) {
-            return true
-        }
-        return false
-    }
-
-    @objc func pasteImage() {
-
-        let pasteboard = UIPasteboard.general
-
-        if pasteboard.hasImages {
-
-            let fileName = CCUtility.createFileName("scan.png", fileDate: Date(),
-                                                    fileType: PHAssetMediaType.image,
-                                                    keyFileName: NCGlobal.shared.keyFileNameMask,
-                                                    keyFileNameType: NCGlobal.shared.keyFileNameType,
-                                                    keyFileNameOriginal: NCGlobal.shared.keyFileNameOriginal,
-                                                    forcedNewFileName: true)!
-            let fileNamePath = CCUtility.getDirectoryScan() + "/" + fileName
-
-            guard let image = pasteboard.image?.fixedOrientation() else {
-                return
-            }
-
-            do {
-                try image.pngData()?.write(to: NSURL.fileURL(withPath: fileNamePath), options: .atomic)
-            } catch {
-                return
-            }
-
-            loadImage()
-        }
-    }
-}
-
-// MARK: -  Methods
-
-@available(iOS 13.0, *)
-extension NCScanCollectionView: UICollectionViewDataSource {
-
-    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
-
-        return collectionView == collectionViewSource ? itemsSource.count : imagesDestination.count
-    }
-
-    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
-
-        if collectionView == collectionViewSource {
-
-            let cell = (collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as? NCScanCell)!
-
-            let fileNamePath = CCUtility.getDirectoryScan() + "/" + itemsSource[indexPath.row]
-
-            guard let data = try? Data(contentsOf: URL(fileURLWithPath: fileNamePath)) else {
-                return cell
-            }
-
-            guard var image = UIImage(data: data) else {
-                return cell
-            }
-
-            let imageWidthInPixels = image.size.width * image.scale
-            let imageHeightInPixels = image.size.height * image.scale
-
-            // 72 DPI
-            if imageWidthInPixels > 595 || imageHeightInPixels > 842 {
-                image = image.resizeImage(size: CGSize(width: 595, height: 842), isAspectRation: true) ?? image
-            }
-
-            cell.customImageView?.image = image
-            cell.delete.action(for: .touchUpInside) { sender in
-
-                let buttonPosition: CGPoint = (sender as? UIButton)!.convert(.zero, to: self.collectionViewSource)
-                if let indexPath = self.collectionViewSource.indexPathForItem(at: buttonPosition) {
-
-                    let fileNameAtPath = CCUtility.getDirectoryScan() + "/" + self.itemsSource[indexPath.row]
-                    CCUtility.removeFile(atPath: fileNameAtPath)
-                    self.itemsSource.remove(at: indexPath.row)
-
-                    self.collectionViewSource.deleteItems(at: [indexPath])
-                }
-            }
-
-            return cell
-
-        } else {
-
-            let cell = (collectionView.dequeueReusableCell(withReuseIdentifier: "cell2", for: indexPath) as? NCScanCell)!
-
-            var image = imagesDestination[indexPath.row]
-
-            let imageWidthInPixels = image.size.width * image.scale
-            let imageHeightInPixels = image.size.height * image.scale
-
-            // 72 DPI 
-            if imageWidthInPixels > 595 || imageHeightInPixels > 842 {
-                image = image.resizeImage(size: CGSize(width: 595, height: 842), isAspectRation: true) ?? image
-            }
-
-            cell.customImageView?.image = self.filter(image: image)
-            cell.customLabel.text = NSLocalizedString("_scan_document_pdf_page_", comment: "") + " " + "\(indexPath.row+1)"
-            cell.delete.action(for: .touchUpInside) { sender in
-
-                let buttonPosition: CGPoint = (sender as? UIButton)!.convert(.zero, to: self.collectionViewDestination)
-                if let indexPath = self.collectionViewDestination.indexPathForItem(at: buttonPosition) {
-
-                    self.imagesDestination.remove(at: indexPath.row)
-                    self.itemsDestination.remove(at: indexPath.row)
-                    
-                    self.collectionViewDestination.deleteItems(at: [indexPath])
-
-                    // Save button
-                    if self.imagesDestination.count == 0 {
-                        self.save.isEnabled = false
-                    } else {
-                        self.save.isEnabled = true
-                    }
-                }
-            }
-            cell.rotate.action(for: .touchUpInside) { sender in
-
-                let buttonPosition: CGPoint = (sender as? UIButton)!.convert(.zero, to: self.collectionViewDestination)
-                if let indexPath = self.collectionViewDestination.indexPathForItem(at: buttonPosition), let cell = self.collectionViewDestination.cellForItem(at: indexPath) as? NCScanCell {
-                    
-                    var image = self.imagesDestination[indexPath.row]
-                    image = image.rotate(radians: .pi/2)!
-                    self.imagesDestination[indexPath.row] = image
-                    
-                    cell.customImageView.image = image
-                }
-            }
-
-            return cell
-        }
-    }
-}
-
-extension UIImage {
-    func rotate(radians: Float) -> UIImage? {
-        var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
-        // Trim off the extremely small float value to prevent core graphics from rounding it up
-        newSize.width = floor(newSize.width)
-        newSize.height = floor(newSize.height)
-
-        UIGraphicsBeginImageContextWithOptions(newSize, true, self.scale)
-        let context = UIGraphicsGetCurrentContext()!
-
-        // Move origin to middle
-        context.translateBy(x: newSize.width/2, y: newSize.height/2)
-        // Rotate around middle
-        context.rotate(by: CGFloat(radians))
-        // Draw the image at its center
-        self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))
-
-        let newImage = UIGraphicsGetImageFromCurrentImageContext()
-        UIGraphicsEndImageContext()
-
-        return newImage
-    }
-}
-
-// MARK: -
-
-@available(iOS 13.0, *)
-extension NCScanCollectionView: UICollectionViewDragDelegate {
-    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
-
-        if collectionView == collectionViewSource {
-            let item = itemsSource[indexPath.row]
-            let itemProvider = NSItemProvider(object: item as NSString)
-            let dragItem = UIDragItem(itemProvider: itemProvider)
-
-            dragItem.localObject = item
-
-            return [dragItem]
-
-        } else {
-            let item = imagesDestination[indexPath.row]
-            let itemProvider = NSItemProvider(object: item as UIImage)
-            let dragItem = UIDragItem(itemProvider: itemProvider)
-
-            dragItem.localObject = item
-
-            return [dragItem]
-        }
-    }
-
-    func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
-
-        if collectionView == collectionViewSource {
-            let item = itemsSource[indexPath.row]
-            let itemProvider = NSItemProvider(object: item as NSString)
-            let dragItem = UIDragItem(itemProvider: itemProvider)
-
-            dragItem.localObject = item
-
-            return [dragItem]
-
-        } else {
-            let item = imagesDestination[indexPath.row]
-            let itemProvider = NSItemProvider(object: item as UIImage)
-            let dragItem = UIDragItem(itemProvider: itemProvider)
-
-            dragItem.localObject = item
-
-            return [dragItem]
-        }
-    }
-
-    func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters? {
-
-        let previewParameters = UIDragPreviewParameters()
-        if collectionView == collectionViewSource {
-            previewParameters.visiblePath = UIBezierPath(rect: CGRect(x: 20, y: 20, width: 100, height: 100))
-        } else {
-            previewParameters.visiblePath = UIBezierPath(rect: CGRect(x: 20, y: 20, width: 80, height: 80))
-        }
-
-        return previewParameters
-    }
-}
-
-// MARK: -
-
-@available(iOS 13.0, *)
-extension NCScanCollectionView: UICollectionViewDropDelegate {
-
-    func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
-
-        return true // session.canLoadObjects(ofClass: NSString.self)
-    }
-
-    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
-
-        if collectionView == collectionViewSource {
-
-            if collectionView.hasActiveDrag {
-                return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
-            } else {
-                return UICollectionViewDropProposal(operation: .forbidden)
-            }
-
-        } else {
-
-            if collectionView.hasActiveDrag {
-                return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
-            } else {
-                return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
-            }
-        }
-    }
-
-    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
-
-        let destinationIndexPath: IndexPath
-
-        switch coordinator.proposal.operation {
-
-        case .move:
-
-            if let indexPath = coordinator.destinationIndexPath {
-
-                destinationIndexPath = indexPath
-
-            } else {
-
-                // Get last index path of table view.
-                let section = collectionView.numberOfSections - 1
-                let row = collectionView.numberOfItems(inSection: section)
-
-                destinationIndexPath = IndexPath(row: row, section: section)
-            }
-            self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
-
-            break
-
-        case .copy:
-
-            // Get last index path of table view.
-            let section = collectionView.numberOfSections - 1
-            let row = collectionView.numberOfItems(inSection: section)
-
-            destinationIndexPath = IndexPath(row: row, section: section)
-            self.copyItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
-
-            break
-
-        default:
-            return
-        }
-    }
-
-    func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) {
-
-        collectionViewDestination.reloadData()
-
-        // Save button
-        if imagesDestination.count == 0 {
-            save.isEnabled = false
-        } else {
-            save.isEnabled = true
-        }
-    }
-}