marinofaggiana 5 vuotta sitten
vanhempi
commit
5c6379be64

+ 32 - 0
Nextcloud.xcodeproj/project.pbxproj

@@ -379,6 +379,12 @@
 		F774DF0F1FCC26BE002AF9FC /* iTunesArtwork@1x.png in Resources */ = {isa = PBXBuildFile; fileRef = F774DF0C1FCC26BD002AF9FC /* iTunesArtwork@1x.png */; };
 		F774DF101FCC26BE002AF9FC /* iTunesArtwork@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F774DF0D1FCC26BD002AF9FC /* iTunesArtwork@2x.png */; };
 		F774DF111FCC26BE002AF9FC /* iTunesArtwork@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F774DF0E1FCC26BE002AF9FC /* iTunesArtwork@3x.png */; };
+		F774F5D12407F4EF000C5E86 /* NCViewerImageView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F774F5CC2407F4EF000C5E86 /* NCViewerImageView.xib */; };
+		F774F5D22407F4EF000C5E86 /* NCViewerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F774F5CD2407F4EF000C5E86 /* NCViewerImageView.swift */; };
+		F774F5D32407F4EF000C5E86 /* NCViewerImageCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F774F5CE2407F4EF000C5E86 /* NCViewerImageCollectionViewCell.xib */; };
+		F774F5D42407F4EF000C5E86 /* NCViewerImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F774F5CF2407F4EF000C5E86 /* NCViewerImageCollectionViewCell.swift */; };
+		F774F5D52407F4EF000C5E86 /* NCViewerImageNibLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F774F5D02407F4EF000C5E86 /* NCViewerImageNibLoadingView.swift */; };
+		F774F5D72407F547000C5E86 /* NCViewerImageAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = F774F5D62407F547000C5E86 /* NCViewerImageAsset.swift */; };
 		F77B0DF21D118A16002130FE /* CCUploadFromOtherUpp.m in Sources */ = {isa = PBXBuildFile; fileRef = F7956FCA1B4886E60085DEA3 /* CCUploadFromOtherUpp.m */; };
 		F77B0DF41D118A16002130FE /* CCMain.m in Sources */ = {isa = PBXBuildFile; fileRef = F70211FB1BAC56E9003FC03E /* CCMain.m */; };
 		F77B0DF51D118A16002130FE /* CCUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = F7053E3D1C639DF500741EA5 /* CCUtility.m */; };
@@ -1099,6 +1105,12 @@
 		F774DF0C1FCC26BD002AF9FC /* iTunesArtwork@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork@1x.png"; sourceTree = "<group>"; };
 		F774DF0D1FCC26BD002AF9FC /* iTunesArtwork@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork@2x.png"; sourceTree = "<group>"; };
 		F774DF0E1FCC26BE002AF9FC /* iTunesArtwork@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork@3x.png"; sourceTree = "<group>"; };
+		F774F5CC2407F4EF000C5E86 /* NCViewerImageView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCViewerImageView.xib; sourceTree = "<group>"; };
+		F774F5CD2407F4EF000C5E86 /* NCViewerImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerImageView.swift; sourceTree = "<group>"; };
+		F774F5CE2407F4EF000C5E86 /* NCViewerImageCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCViewerImageCollectionViewCell.xib; sourceTree = "<group>"; };
+		F774F5CF2407F4EF000C5E86 /* NCViewerImageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerImageCollectionViewCell.swift; sourceTree = "<group>"; };
+		F774F5D02407F4EF000C5E86 /* NCViewerImageNibLoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerImageNibLoadingView.swift; sourceTree = "<group>"; };
+		F774F5D62407F547000C5E86 /* NCViewerImageAsset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCViewerImageAsset.swift; sourceTree = "<group>"; };
 		F777F0301C29717F00CE81CB /* PHAsset+Utility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "PHAsset+Utility.h"; sourceTree = "<group>"; };
 		F777F0311C29717F00CE81CB /* PHAsset+Utility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "PHAsset+Utility.m"; sourceTree = "<group>"; };
 		F77D49A71DC238E500CDC568 /* loading.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = loading.gif; sourceTree = "<group>"; };
@@ -2106,6 +2118,19 @@
 			path = "File Provider Extension";
 			sourceTree = "<group>";
 		};
+		F774F5CB2407F4EF000C5E86 /* NCViewerImage */ = {
+			isa = PBXGroup;
+			children = (
+				F774F5D62407F547000C5E86 /* NCViewerImageAsset.swift */,
+				F774F5CC2407F4EF000C5E86 /* NCViewerImageView.xib */,
+				F774F5CD2407F4EF000C5E86 /* NCViewerImageView.swift */,
+				F774F5CE2407F4EF000C5E86 /* NCViewerImageCollectionViewCell.xib */,
+				F774F5CF2407F4EF000C5E86 /* NCViewerImageCollectionViewCell.swift */,
+				F774F5D02407F4EF000C5E86 /* NCViewerImageNibLoadingView.swift */,
+			);
+			path = NCViewerImage;
+			sourceTree = "<group>";
+		};
 		F78ACD3E21903BA20088454D /* Cell */ = {
 			isa = PBXGroup;
 			children = (
@@ -2168,6 +2193,7 @@
 		F79630EC215526B60015EEA5 /* Viewer */ = {
 			isa = PBXGroup;
 			children = (
+				F774F5CB2407F4EF000C5E86 /* NCViewerImage */,
 				F72D404823D2082500A97FD0 /* NCViewerNextcloudText.swift */,
 				F710D1F42405770F00A6033D /* NCViewerPDF.swift */,
 				F790110D21415BF600D7B136 /* NCViewerRichdocument.swift */,
@@ -3025,6 +3051,7 @@
 				F7F54CF31E5B14C700E19C62 /* ImageSelectedSmallOn@3x.png in Resources */,
 				F7F54CFA1E5B14C700E19C62 /* UIBarButtonItemArrowLeft.png in Resources */,
 				F7FCFFD81D70798C000E6E29 /* CCPeekPop.storyboard in Resources */,
+				F774F5D32407F4EF000C5E86 /* NCViewerImageCollectionViewCell.xib in Resources */,
 				F7F54CF61E5B14C700E19C62 /* PlayButtonOverlayLarge@3x.png in Resources */,
 				F78F74342163757000C2ADAD /* NCTrash.storyboard in Resources */,
 				F760F78521F21F61006B1A73 /* EmojiCollectionViewCell.xib in Resources */,
@@ -3100,6 +3127,7 @@
 				F7AE00FA230E81EB007ACF8A /* NCBrowserWeb.storyboard in Resources */,
 				F7F54CE91E5B14C700E19C62 /* ImageSelectedOff@2x.png in Resources */,
 				F77B0F8A1D118A16002130FE /* CCCellMain.xib in Resources */,
+				F774F5D12407F4EF000C5E86 /* NCViewerImageView.xib in Resources */,
 				F78ACD58219048040088454D /* NCSectionHeaderMenu.xib in Resources */,
 				F77B0F8C1D118A16002130FE /* CCCellMainTransfer.xib in Resources */,
 				F73B4EF41F470D9100BBEE4B /* JISFreq.tab in Resources */,
@@ -3402,6 +3430,7 @@
 				F760F79121F21F61006B1A73 /* EmojiCollectionViewCell.swift in Sources */,
 				F750374F1DBFA91A008FB480 /* NSArray+PureLayout.m in Sources */,
 				F77444F8222816D5000D5EB0 /* NCPhotosPickerViewController.swift in Sources */,
+				F774F5D42407F4EF000C5E86 /* NCViewerImageCollectionViewCell.swift in Sources */,
 				F77B0E141D118A16002130FE /* CCError.m in Sources */,
 				F7E09CE523E3088C00FB3E9E /* NCSplitViewController.swift in Sources */,
 				F73B4F131F470D9100BBEE4B /* nsUniversalDetector.cpp in Sources */,
@@ -3445,12 +3474,14 @@
 				F762CAFC1EACB66200B38484 /* XLFormImageCell.m in Sources */,
 				F72D1007210B6882009C96B7 /* NCPushNotificationEncryption.m in Sources */,
 				F70022D11EC4C9100080073F /* OCUserProfile.m in Sources */,
+				F774F5D72407F547000C5E86 /* NCViewerImageAsset.swift in Sources */,
 				F73B4EF61F470D9100BBEE4B /* LangArabicModel.cpp in Sources */,
 				F7CA1ED420E7E3FE002CC65E /* PKPendingView.m in Sources */,
 				F7DFB7E0219C312D00680748 /* NCRichDocumentTemplate.m in Sources */,
 				F769454222E9F0EE000A798A /* NCShareLinkMenuView.swift in Sources */,
 				F73B4F0B1F470D9100BBEE4B /* nsGB2312Prober.cpp in Sources */,
 				F762CAFE1EACB66200B38484 /* XLFormLeftRightSelectorCell.m in Sources */,
+				F774F5D52407F4EF000C5E86 /* NCViewerImageNibLoadingView.swift in Sources */,
 				F77B0E301D118A16002130FE /* CCHud.m in Sources */,
 				F76673ED22C901F6007ED366 /* FileProviderDomain.swift in Sources */,
 				F70022E91EC4C9100080073F /* OCXMLShareByLinkParser.m in Sources */,
@@ -3560,6 +3591,7 @@
 				F762CB151EACB66200B38484 /* XLFormRowNavigationAccessoryView.m in Sources */,
 				F762CB0A1EACB66200B38484 /* XLFormDescriptor.m in Sources */,
 				F7020FCE2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift in Sources */,
+				F774F5D22407F4EF000C5E86 /* NCViewerImageView.swift in Sources */,
 				F7F4B1D823C74B3E00D82A6E /* NCRichWorkspace.swift in Sources */,
 				F726EEEC1FED1C820030B9C8 /* NCEndToEndInitialize.swift in Sources */,
 				F79A65C62191D95E00FF6DCC /* NCSelect.swift in Sources */,

+ 8 - 8
iOSClient/Main/Main.storyboard

@@ -161,23 +161,24 @@
                         <rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
-                            <imageView tag="999" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" preservesSuperviewLayoutMargins="YES" image="logo" translatesAutoresizingMaskIntoConstraints="NO" id="zlU-MP-ZVs">
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Hbz-Fn-lvX" customClass="NCViewerImageView" customModule="Nextcloud" customModuleProvider="target">
                                 <rect key="frame" x="0.0" y="0.0" width="414" height="774"/>
-                            </imageView>
+                                <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                            </view>
                         </subviews>
                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                         <constraints>
-                            <constraint firstItem="3ph-Od-2hO" firstAttribute="trailing" secondItem="zlU-MP-ZVs" secondAttribute="trailing" id="GuY-bH-GIn"/>
-                            <constraint firstItem="zlU-MP-ZVs" firstAttribute="top" secondItem="3ph-Od-2hO" secondAttribute="top" id="HSh-HF-TAJ"/>
-                            <constraint firstItem="3ph-Od-2hO" firstAttribute="bottom" secondItem="zlU-MP-ZVs" secondAttribute="bottom" id="hvN-HY-D9V"/>
-                            <constraint firstItem="zlU-MP-ZVs" firstAttribute="leading" secondItem="3ph-Od-2hO" secondAttribute="leading" id="sVy-C6-hu1"/>
+                            <constraint firstItem="Hbz-Fn-lvX" firstAttribute="top" secondItem="3ph-Od-2hO" secondAttribute="top" id="3QY-40-Fyy"/>
+                            <constraint firstItem="3ph-Od-2hO" firstAttribute="bottom" secondItem="Hbz-Fn-lvX" secondAttribute="bottom" id="6eE-3t-YVt"/>
+                            <constraint firstItem="3ph-Od-2hO" firstAttribute="trailing" secondItem="Hbz-Fn-lvX" secondAttribute="trailing" id="R7K-EW-Hj6"/>
+                            <constraint firstItem="Hbz-Fn-lvX" firstAttribute="leading" secondItem="3ph-Od-2hO" secondAttribute="leading" id="z1o-rc-vf1"/>
                         </constraints>
                         <viewLayoutGuide key="safeArea" id="3ph-Od-2hO"/>
                     </view>
                     <extendedEdge key="edgesForExtendedLayout"/>
                     <navigationItem key="navigationItem" id="cJm-UN-Dvj" userLabel="Detail"/>
                     <connections>
-                        <outlet property="backgroundView" destination="zlU-MP-ZVs" id="yVd-Vz-gsJ"/>
+                        <outlet property="viewerImageView" destination="Hbz-Fn-lvX" id="19q-nj-ROU"/>
                     </connections>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="IJ0-oL-QKv" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -649,7 +650,6 @@
     <resources>
         <image name="avatar" width="25" height="25"/>
         <image name="disclosureIndicator" width="26" height="28"/>
-        <image name="logo" width="223" height="157.5"/>
         <image name="tabBarPlus" width="80" height="80"/>
     </resources>
     <inferredMetricsTieBreakers>

+ 31 - 19
iOSClient/Main/NCDetailViewController.swift

@@ -27,7 +27,7 @@ import NCCommunication
 
 class NCDetailViewController: UIViewController {
     
-    @IBOutlet weak var backgroundView: UIImageView!
+    @IBOutlet weak var viewerImageView: NCViewerImageView!
     
     @objc var metadata: tableMetadata?
     @objc var selector: String?
@@ -78,7 +78,7 @@ class NCDetailViewController: UIViewController {
                     navigationController.popToRootViewController(animated: true)
                 }
             } else {
-                for view in backgroundView.subviews {
+                for view in viewerImageView.subviews {
                     view.removeFromSuperview()
                 }
                 self.navigationController?.navigationBar.topItem?.title = ""
@@ -87,12 +87,12 @@ class NCDetailViewController: UIViewController {
     }
     
     @objc func changeTheming() {
-        backgroundView.image = CCGraphics.changeThemingColorImage(UIImage.init(named: "logo"), multiplier: 2, color: NCBrandColor.sharedInstance.brand.withAlphaComponent(0.4))
+        //backgroundView.image = CCGraphics.changeThemingColorImage(UIImage.init(named: "logo"), multiplier: 2, color: NCBrandColor.sharedInstance.brand.withAlphaComponent(0.4))
         view.backgroundColor = NCBrandColor.sharedInstance.backgroundView
     }
     
     func subViewActive() -> UIView? {
-        return backgroundView.subviews.first
+        return viewerImageView.subviews.first
     }
     
     @objc func viewFile(metadata: tableMetadata, selector: String?) {
@@ -113,18 +113,30 @@ class NCDetailViewController: UIViewController {
         
         // IMAGE
         if metadata.typeFile == k_metadataTypeFile_image {
+            if let metadatas = NCManageDatabase.sharedInstance.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND typeFile == %@", metadata.account, metadata.serverUrl, k_metadataTypeFile_image), sorted: "fileName", ascending: true) {
+                var assets: [NCViewerImageAsset?] = [NCViewerImageAsset]()
+                for metadata in metadatas {
+                    let imagePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+                    if let image = UIImage(contentsOfFile: imagePath) {
+                        let asset = NCViewerImageAsset(image: image)
+                        assets.append(asset)
+                    }
+                }
+                viewerImageView.assets = assets
+                //viewerImageView.preselectItem(at: 0)
+            }
             return
         }
         
         // AUDIO VIDEO
         if metadata.typeFile == k_metadataTypeFile_audio || metadata.typeFile == k_metadataTypeFile_video {
-            NCViewerMedia.sharedInstance.viewMedia(metadata, view: backgroundView)
+            NCViewerMedia.sharedInstance.viewMedia(metadata, view: viewerImageView)
             return
         }
         
         // DOCUMENT - INTERNAL VIEWER
         if metadata.typeFile == k_metadataTypeFile_document && selector != nil && selector == selectorLoadFileInternalView {
-            NCViewerDocumentWeb.sharedInstance.viewDocumentWebAt(metadata, view: backgroundView)
+            NCViewerDocumentWeb.sharedInstance.viewDocumentWebAt(metadata, view: viewerImageView)
             return
         }
         
@@ -134,14 +146,14 @@ class NCDetailViewController: UIViewController {
             // PDF
             if metadata.contentType == "application/pdf" {
                 if #available(iOS 11.0, *) {
-                    let viewerPDF = NCViewerPDF.init(frame: backgroundView.frame)
+                    let viewerPDF = NCViewerPDF.init(frame: viewerImageView.frame)
                     
                     let filePath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
                     if CCUtility.fileProviderStorageExists(metadata.ocId, fileNameView: metadata.fileNameView) == false {
                         return
                     }
                     
-                    viewerPDF.setupPdfView(filePath: URL(fileURLWithPath: filePath), view: backgroundView)
+                    viewerPDF.setupPdfView(filePath: URL(fileURLWithPath: filePath), view: viewerImageView)
                 }
                 
                 return
@@ -153,7 +165,7 @@ class NCDetailViewController: UIViewController {
                 let editor = NCUtility.sharedInstance.isDirectEditing(metadata)!
                 if editor == k_editor_text || editor == k_editor_onlyoffice {
                     
-                    NCUtility.sharedInstance.startActivityIndicator(view: backgroundView, bottom: 0)
+                    NCUtility.sharedInstance.startActivityIndicator(view: viewerImageView, bottom: 0)
 
                     if metadata.url == "" {
                         
@@ -168,8 +180,8 @@ class NCDetailViewController: UIViewController {
                             
                             if errorCode == 0 && account == self.appDelegate.activeAccount && url != nil {
                                 
-                                let nextcloudText = NCViewerNextcloudText.init(frame: self.backgroundView.frame, configuration: WKWebViewConfiguration())
-                                nextcloudText.viewerAt(url!, metadata: metadata, editor: editor, view: self.backgroundView, viewController: self)
+                                let nextcloudText = NCViewerNextcloudText.init(frame: self.viewerImageView.frame, configuration: WKWebViewConfiguration())
+                                nextcloudText.viewerAt(url!, metadata: metadata, editor: editor, view: self.viewerImageView, viewController: self)
                                 if editor == k_editor_text && self.splitViewController!.isCollapsed {
                                     self.navigationController?.navigationItem.hidesBackButton = true
                                 }
@@ -187,8 +199,8 @@ class NCDetailViewController: UIViewController {
                         
                     } else {
                         
-                        let nextcloudText = NCViewerNextcloudText.init(frame: backgroundView.frame, configuration: WKWebViewConfiguration())
-                        nextcloudText.viewerAt(metadata.url, metadata: metadata, editor: editor, view: backgroundView, viewController: self)
+                        let nextcloudText = NCViewerNextcloudText.init(frame: viewerImageView.frame, configuration: WKWebViewConfiguration())
+                        nextcloudText.viewerAt(metadata.url, metadata: metadata, editor: editor, view: viewerImageView, viewController: self)
                         if editor == k_editor_text && self.splitViewController!.isCollapsed {
                             self.navigationController?.navigationItem.hidesBackButton = true
                         }
@@ -201,7 +213,7 @@ class NCDetailViewController: UIViewController {
             // RichDocument: Collabora
             if NCUtility.sharedInstance.isRichDocument(metadata) && appDelegate.reachability.isReachable() {
                 
-                NCUtility.sharedInstance.startActivityIndicator(view: backgroundView, bottom: 0)
+                NCUtility.sharedInstance.startActivityIndicator(view: viewerImageView, bottom: 0)
                 
                 if metadata.url == "" {
                     
@@ -209,8 +221,8 @@ class NCDetailViewController: UIViewController {
                         
                         if errorCode == 0 && account == self.appDelegate.activeAccount && url != nil {
                             
-                            let richDocument = NCViewerRichdocument.init(frame: self.backgroundView.frame, configuration: WKWebViewConfiguration())
-                            richDocument.viewRichDocumentAt(url!, metadata: metadata, view: self.backgroundView, viewController: self)
+                            let richDocument = NCViewerRichdocument.init(frame: self.viewerImageView.frame, configuration: WKWebViewConfiguration())
+                            richDocument.viewRichDocumentAt(url!, metadata: metadata, view: self.viewerImageView, viewController: self)
                             if self.splitViewController != nil && self.splitViewController!.isCollapsed {
                                 self.navigationController?.navigationItem.hidesBackButton = true
                             }
@@ -229,8 +241,8 @@ class NCDetailViewController: UIViewController {
                     
                 } else {
                     
-                    let richDocument = NCViewerRichdocument.init(frame: backgroundView.frame, configuration: WKWebViewConfiguration())
-                    richDocument.viewRichDocumentAt(metadata.url, metadata: metadata, view: backgroundView, viewController: self)
+                    let richDocument = NCViewerRichdocument.init(frame: viewerImageView.frame, configuration: WKWebViewConfiguration())
+                    richDocument.viewRichDocumentAt(metadata.url, metadata: metadata, view: viewerImageView, viewController: self)
                     if self.splitViewController != nil && self.splitViewController!.isCollapsed {
                         self.navigationController?.navigationItem.hidesBackButton = true
                     }
@@ -239,7 +251,7 @@ class NCDetailViewController: UIViewController {
         }
         
         // OTHER
-        NCViewerDocumentWeb.sharedInstance.viewDocumentWebAt(metadata, view: backgroundView)
+        NCViewerDocumentWeb.sharedInstance.viewDocumentWebAt(metadata, view: viewerImageView)
     }
 
 }

+ 78 - 0
iOSClient/Viewer/NCViewerImage/NCViewerImageAsset.swift

@@ -0,0 +1,78 @@
+
+import UIKit
+
+public class NCViewerImageAsset: NSObject {
+
+    public enum ImageType {
+        case jpg
+        case gif
+
+        static func from(mimeType: String) -> ImageType? {
+            if mimeType.contains("gif") { return .gif
+            } else if mimeType.contains("jpg") { return .jpg }
+            return nil
+        }
+    }
+
+    public var url: URL?
+    public var image: UIImage?
+    public var type: ImageType?
+    public var caption: String?
+
+    private override init() { }
+
+    public init(url: URL) {
+        self.url = url
+    }
+
+    public init(url: URL, caption: String?) {
+        self.url = url
+        self.caption = caption
+    }
+
+    public init(image: UIImage) {
+        self.image = image
+    }
+
+    public init(image: UIImage, caption: String?) {
+        self.image = image
+        self.caption = caption
+    }
+
+    func download(completion:@escaping(_ success: Bool?) -> Void) -> URLSessionDataTask? {
+        return NCViewerImageAsset.download(url: url) { (success, image, type)  in
+            self.image = image
+            if let type = type {
+                self.type = type
+            }
+            completion(success)
+        }
+    }
+
+    static func download(url: URL?, completion:@escaping(_ success: Bool?, _ image: UIImage?, _ type: NCViewerImageAsset.ImageType?) -> Void) -> URLSessionDataTask? {
+        guard let url = url else {
+            completion(false, nil, nil)
+            return nil
+        }
+        let dataTask = URLSession.shared.dataTask(with: url) { data, response, error  in
+            guard let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
+                let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
+                let data = data, error == nil,
+                var image = UIImage(data: data)
+                else {
+                    DispatchQueue.main.async { completion(false, nil, nil) }
+                    return
+            }
+            let type: NCViewerImageAsset.ImageType? = ImageType.from(mimeType: mimeType)
+            /*
+            if type == .gif, let gif = UIImage.gif(data: data) {
+                image = gif
+            }
+            */
+            DispatchQueue.main.async { completion(true, image, type) }
+        }
+        dataTask.resume()
+        return dataTask
+    }
+
+}

+ 145 - 0
iOSClient/Viewer/NCViewerImage/NCViewerImageCollectionViewCell.swift

@@ -0,0 +1,145 @@
+
+import UIKit
+
+protocol NCViewerImageCollectionViewCellDelegate: class {
+    func didStartZooming(_ cell: NCViewerImageCollectionViewCell)
+}
+
+class NCViewerImageCollectionViewCell: UICollectionViewCell {
+    static var reusableIdentifier: String = "NCViewerImageCollectionViewCell"
+
+    private var dataTask: URLSessionDataTask?
+    weak var delegate: NCViewerImageCollectionViewCellDelegate?
+
+    @IBOutlet weak var imageWidthConstraint: NSLayoutConstraint!
+    @IBOutlet weak var imageHeightConstraint: NSLayoutConstraint!
+    @IBOutlet weak var topConstraint: NSLayoutConstraint!
+    @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
+    @IBOutlet weak var scrollView: UIScrollView!
+    @IBOutlet weak var galleryImageView: UIImageView!
+    @IBOutlet weak var leadingConstraint: NSLayoutConstraint!
+    @IBOutlet weak var trailingConstraint: NSLayoutConstraint!
+
+    private var observer: NSKeyValueObservation?
+
+    func withImageAsset(_ asset: NCViewerImageAsset?) {
+        guard self.dataTask?.state != URLSessionDataTask.State.running else { return }
+        guard let asset = asset else { return }
+        if asset.image != nil {
+            self.apply(image: self.fitIntoFrame(image: asset.image, type: asset.type))
+        } else if asset.url != nil {
+            self.galleryImageView.image = nil
+            self.dataTask = asset.download(completion: { _ in
+                self.apply(image: self.fitIntoFrame(image: asset.image, type: asset.type))
+            })
+        }
+    }
+
+    func apply(image: UIImage?) {
+        guard let image = image else { return }
+        self.galleryImageView.alpha = 0
+        self.galleryImageView.image = image
+        UIView.animate(withDuration: 0.1) {
+            self.galleryImageView.alpha = 1
+        }
+    }
+
+    override func draw(_ rect: CGRect) {
+        super.draw(rect)
+        self.scrollView.maximumZoomScale = 4
+        self.redrawConstraintIfNeeded()
+        self.observer = self.observe(\.bounds, options: NSKeyValueObservingOptions.new, changeHandler: { (_, _) in
+            self.apply(image: self.fitIntoFrame(image: self.galleryImageView.image, type: nil))
+            self.redrawConstraintIfNeeded()
+        })
+    }
+
+    func cancelPendingDataTask() {
+        self.dataTask?.cancel()
+    }
+
+    override func layoutSubviews() {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
+            self.redrawConstraintIfNeeded()
+        }
+        super.layoutSubviews()
+    }
+
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        self.scrollView.setZoomScale(1, animated: false)
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+            // limitation of cell lifecycle
+            self.redrawConstraintIfNeeded()
+        }
+    }
+
+    func setMargins(vertical: CGFloat, horizontal: CGFloat) {
+        self.topConstraint.constant = vertical
+        self.bottomConstraint.constant = vertical
+        self.leadingConstraint.constant = horizontal
+        self.trailingConstraint.constant = horizontal
+    }
+
+    func redrawConstraintIfNeeded() {
+        let imageHeight = self.galleryImageView.frame.size.height
+        let imageWidth = self.galleryImageView.frame.size.width
+        let spaceLeftVertical = self.scrollView.frame.size.height-imageHeight
+        let spaceLeftHorizontal = self.scrollView.frame.size.width-imageWidth
+        let constraintConstantValueVertical = spaceLeftVertical/2 > 0 ? spaceLeftVertical/2 : 0
+        let constraintConstantValueHorizontal = spaceLeftHorizontal/2 > 0 ? spaceLeftHorizontal/2 : 0
+        self.setMargins(vertical: constraintConstantValueVertical, horizontal: constraintConstantValueHorizontal)
+        self.layoutIfNeeded()
+    }
+
+    private func fitIntoFrame(image: UIImage?, type: NCViewerImageAsset.ImageType?) -> UIImage? {
+        let type: NCViewerImageAsset.ImageType = type ?? .jpg
+        guard let image = image else { return nil }
+        guard image.size != CGSize.zero else { return nil }
+        let screenRatio = UIScreen.main.bounds.size.width/UIScreen.main.bounds.size.height
+        var reqWidth: CGFloat = frame.size.width
+        if image.size.width > reqWidth {
+            reqWidth = image.size.width
+        }
+        let imageRatio = image.size.width/image.size.height
+        if imageRatio < screenRatio {
+            reqWidth = frame.size.height*imageRatio
+        }
+        let size = CGSize(width: reqWidth, height: reqWidth/imageRatio)
+        if imageRatio < screenRatio {
+            self.imageHeightConstraint.constant = frame.size.height
+            self.imageWidthConstraint.constant = frame.size.height*imageRatio
+        } else {
+            self.imageHeightConstraint.constant = frame.size.width/imageRatio
+            self.imageWidthConstraint.constant = frame.size.width
+        }
+        switch type {
+        case .gif: return image
+        case .jpg:
+            UIGraphicsBeginImageContext(size)
+            image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
+            let finalImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
+            UIGraphicsEndImageContext()
+            return finalImage
+        }
+    }
+
+    func redrawImage() {
+        self.apply(image: self.fitIntoFrame(image: self.galleryImageView.image, type: nil))
+        self.redrawConstraintIfNeeded()
+    }
+}
+
+extension NCViewerImageCollectionViewCell: UIScrollViewDelegate {
+    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
+        return self.galleryImageView
+    }
+
+    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
+        self.delegate?.didStartZooming(self)
+    }
+
+    func scrollViewDidZoom(_ scrollView: UIScrollView) {
+        self.redrawConstraintIfNeeded()
+    }
+}

+ 68 - 0
iOSClient/Viewer/NCViewerImage/NCViewerImageCollectionViewCell.xib

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="NCViewerImageCollectionViewCell" customModule="Nextcloud" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="372" height="458"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
+                <rect key="frame" x="0.0" y="0.0" width="372" height="458"/>
+                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                <subviews>
+                    <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fYl-gF-3Tc">
+                        <rect key="frame" x="0.0" y="0.0" width="372" height="458"/>
+                        <subviews>
+                            <imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="3" translatesAutoresizingMaskIntoConstraints="NO" id="Y2R-g3-1to">
+                                <rect key="frame" x="0.0" y="0.0" width="372" height="375"/>
+                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="375" id="buA-yt-EU1"/>
+                                    <constraint firstAttribute="width" constant="372" id="dup-Ci-FrN"/>
+                                </constraints>
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                        <constraints>
+                            <constraint firstAttribute="trailing" secondItem="Y2R-g3-1to" secondAttribute="trailing" priority="250" id="Q4y-Pe-Blg"/>
+                            <constraint firstItem="Y2R-g3-1to" firstAttribute="top" secondItem="fYl-gF-3Tc" secondAttribute="top" priority="250" id="S31-PI-s22"/>
+                            <constraint firstItem="Y2R-g3-1to" firstAttribute="leading" secondItem="fYl-gF-3Tc" secondAttribute="leading" priority="250" id="l8D-LP-ERM"/>
+                            <constraint firstAttribute="bottom" secondItem="Y2R-g3-1to" secondAttribute="bottom" priority="250" id="u4J-cI-jJ1"/>
+                        </constraints>
+                        <connections>
+                            <outlet property="delegate" destination="gTV-IL-0wX" id="4Eo-bw-Wov"/>
+                        </connections>
+                    </scrollView>
+                </subviews>
+            </view>
+            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+            <constraints>
+                <constraint firstAttribute="bottom" secondItem="fYl-gF-3Tc" secondAttribute="bottom" id="EGd-Qa-dtz"/>
+                <constraint firstItem="fYl-gF-3Tc" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="RSp-db-iCz"/>
+                <constraint firstItem="fYl-gF-3Tc" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="cgT-tQ-4MD"/>
+                <constraint firstAttribute="trailing" secondItem="fYl-gF-3Tc" secondAttribute="trailing" id="plj-M5-9uV"/>
+            </constraints>
+            <size key="customSize" width="372" height="458"/>
+            <connections>
+                <outlet property="bottomConstraint" destination="u4J-cI-jJ1" id="apG-eS-WzL"/>
+                <outlet property="galleryImageView" destination="Y2R-g3-1to" id="uWL-Dw-oXx"/>
+                <outlet property="imageHeightConstraint" destination="buA-yt-EU1" id="OGS-lC-INp"/>
+                <outlet property="imageWidthConstraint" destination="dup-Ci-FrN" id="8cX-Dl-FHP"/>
+                <outlet property="leadingConstraint" destination="l8D-LP-ERM" id="rQa-OZ-pFo"/>
+                <outlet property="scrollView" destination="fYl-gF-3Tc" id="veY-z3-1mI"/>
+                <outlet property="topConstraint" destination="S31-PI-s22" id="DO9-Zk-F38"/>
+                <outlet property="trailingConstraint" destination="Q4y-Pe-Blg" id="hrk-zy-L3s"/>
+            </connections>
+            <point key="canvasLocation" x="195" y="258"/>
+        </collectionViewCell>
+    </objects>
+    <resources>
+        <image name="3" width="592.5" height="1555.5"/>
+    </resources>
+</document>

+ 42 - 0
iOSClient/Viewer/NCViewerImage/NCViewerImageNibLoadingView.swift

@@ -0,0 +1,42 @@
+
+import UIKit
+
+@IBDesignable
+public class NCViewerImageNibLoadingView: UIView {
+
+    @IBOutlet weak var view: UIView!
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        view = NCViewerImageNibLoading.nibSetup(self)
+    }
+
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        view = NCViewerImageNibLoading.nibSetup(self)
+    }
+
+    public override func layoutSubviews() {
+        super.layoutSubviews()
+        self.view.backgroundColor = .clear
+    }
+}
+
+private class NCViewerImageNibLoading: NSObject {
+    class func loadViewFromNib(_ obj: UIView) -> UIView {
+        let bundle = Bundle(for: type(of: obj))
+        let nib = UINib(nibName: String(describing: type(of: obj)), bundle: bundle)
+        let nibView = (nib.instantiate(withOwner: obj, options: nil).first as? UIView)!
+        return nibView
+    }
+
+    class func nibSetup(_ obj: UIView) -> UIView {
+        obj.backgroundColor = .clear
+        let view: UIView = NCViewerImageNibLoading.loadViewFromNib(obj)
+        view.frame = obj.bounds
+        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        view.translatesAutoresizingMaskIntoConstraints = true
+        obj.addSubview(view)
+        return view
+    }
+}

+ 74 - 0
iOSClient/Viewer/NCViewerImage/NCViewerImageView.swift

@@ -0,0 +1,74 @@
+
+import UIKit
+
+public class NCViewerImageView: NCViewerImageNibLoadingView {
+    @IBOutlet weak var collectionView: UICollectionView!
+
+    public var assets: [NCViewerImageAsset?]? {
+        didSet {
+            self.collectionView.reloadData()
+        }
+    }
+
+    private var preselectedIndex: Int = 0
+
+    override public func willMove(toSuperview newSuperview: UIView?) {
+        super.willMove(toSuperview: newSuperview)
+        self.collectionView.register(UINib.init(nibName: String(describing: NCViewerImageCollectionViewCell.self), bundle: Bundle(for: type(of: self))), forCellWithReuseIdentifier: NCViewerImageCollectionViewCell.reusableIdentifier)
+    }
+
+    public override func layoutSubviews() {
+        super.layoutSubviews()
+        self.collectionView.collectionViewLayout.invalidateLayout()
+        if let indexPath = self.collectionView.indexPathsForVisibleItems.last {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+                self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
+            }
+        }
+    }
+
+    public func preselectItem(at index: Int) {
+        self.preselectedIndex = index
+    }
+
+    public override func draw(_ rect: CGRect) {
+        super.draw(rect)
+        self.collectionView.scrollToItem(at: IndexPath(row: self.preselectedIndex, section: 0), at: .centeredHorizontally, animated: false)
+    }
+}
+
+extension NCViewerImageView: UICollectionViewDataSource {
+
+    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return assets?.count ?? 0
+    }
+
+    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        let cell: NCViewerImageCollectionViewCell = (collectionView.dequeueReusableCell(withReuseIdentifier: NCViewerImageCollectionViewCell.reusableIdentifier, for: indexPath) as? NCViewerImageCollectionViewCell)!
+        cell.withImageAsset(assets?[indexPath.row])
+        cell.delegate = self
+        return cell
+    }
+}
+
+extension NCViewerImageView: UICollectionViewDelegate {
+    public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+        (cell as? NCViewerImageCollectionViewCell)?.cancelPendingDataTask()
+    }
+
+    public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+        (cell as? NCViewerImageCollectionViewCell)?.withImageAsset(assets?[indexPath.row])
+    }
+}
+
+extension NCViewerImageView: UICollectionViewDelegateFlowLayout {
+    public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        return CGSize(width: floor(collectionView.frame.size.width), height: floor(collectionView.frame.size.height))
+    }
+}
+
+extension NCViewerImageView: NCViewerImageCollectionViewCellDelegate {
+    func didStartZooming(_ cell: NCViewerImageCollectionViewCell) {
+
+    }
+}

+ 47 - 0
iOSClient/Viewer/NCViewerImage/NCViewerImageView.xib

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina4_7" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="NCViewerImageView" customModule="Nextcloud" customModuleProvider="target">
+            <connections>
+                <outlet property="collectionView" destination="dvH-x4-Vkp" id="CNC-Vg-HLc"/>
+            </connections>
+        </placeholder>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB">
+            <rect key="frame" x="0.0" y="0.0" width="375" height="612"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" pagingEnabled="YES" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="dvH-x4-Vkp">
+                    <rect key="frame" x="0.0" y="0.0" width="375" height="612"/>
+                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="vbZ-6V-yr3">
+                        <size key="itemSize" width="50" height="50"/>
+                        <size key="headerReferenceSize" width="0.0" height="0.0"/>
+                        <size key="footerReferenceSize" width="0.0" height="0.0"/>
+                        <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
+                    </collectionViewFlowLayout>
+                    <connections>
+                        <outlet property="dataSource" destination="-1" id="cbr-e4-idB"/>
+                        <outlet property="delegate" destination="-1" id="e15-QR-GRy"/>
+                    </connections>
+                </collectionView>
+            </subviews>
+            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+            <constraints>
+                <constraint firstAttribute="trailing" secondItem="dvH-x4-Vkp" secondAttribute="trailing" id="8W6-pH-jYH"/>
+                <constraint firstAttribute="bottom" secondItem="dvH-x4-Vkp" secondAttribute="bottom" id="MUe-05-edB"/>
+                <constraint firstItem="dvH-x4-Vkp" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="iWm-ue-m5e"/>
+                <constraint firstItem="dvH-x4-Vkp" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="k3c-b7-PBB"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <point key="canvasLocation" x="-33" y="26"/>
+        </view>
+    </objects>
+</document>