marinofaggiana 5 years ago
parent
commit
02720a4579

+ 4 - 0
Nextcloud.xcodeproj/project.pbxproj

@@ -367,6 +367,7 @@
 		F76B3CCF1EAE01BD00921AC9 /* NCBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76B3CCD1EAE01BD00921AC9 /* NCBrand.swift */; };
 		F76D3CF12428B40E005DFA87 /* NCViewerPDFSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76D3CF02428B40E005DFA87 /* NCViewerPDFSearch.swift */; };
 		F76D3CF32428B94E005DFA87 /* NCViewerPDFSearchCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F76D3CF22428B94E005DFA87 /* NCViewerPDFSearchCell.xib */; };
+		F76D3CF52428D0C1005DFA87 /* NCViewerPDF.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F76D3CF42428D0C0005DFA87 /* NCViewerPDF.storyboard */; };
 		F771E3D320E2392D00AFB62D /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F771E3D220E2392D00AFB62D /* FileProviderExtension.swift */; };
 		F771E3D520E2392D00AFB62D /* FileProviderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F771E3D420E2392D00AFB62D /* FileProviderItem.swift */; };
 		F771E3D720E2392D00AFB62D /* FileProviderEnumerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F771E3D620E2392D00AFB62D /* FileProviderEnumerator.swift */; };
@@ -1032,6 +1033,7 @@
 		F76C3B881C638A4C00DC4301 /* CCError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCError.m; sourceTree = "<group>"; };
 		F76D3CF02428B40E005DFA87 /* NCViewerPDFSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerPDFSearch.swift; sourceTree = "<group>"; };
 		F76D3CF22428B94E005DFA87 /* NCViewerPDFSearchCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCViewerPDFSearchCell.xib; sourceTree = "<group>"; };
+		F76D3CF42428D0C0005DFA87 /* NCViewerPDF.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCViewerPDF.storyboard; sourceTree = "<group>"; };
 		F76E71E42244DF6900690001 /* Zip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Zip.framework; path = Carthage/Build/iOS/Zip.framework; sourceTree = "<group>"; };
 		F76F23321ED4600700C40023 /* Share-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Share-Bridging-Header.h"; sourceTree = "<group>"; };
 		F771E3D020E2392D00AFB62D /* File Provider Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "File Provider Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -2006,6 +2008,7 @@
 		F76D3CEF2428B3DD005DFA87 /* NCViewerPDF */ = {
 			isa = PBXGroup;
 			children = (
+				F76D3CF42428D0C0005DFA87 /* NCViewerPDF.storyboard */,
 				F76D3CF22428B94E005DFA87 /* NCViewerPDFSearchCell.xib */,
 				F710D1F42405770F00A6033D /* NCViewerPDF.swift */,
 				F76D3CF02428B40E005DFA87 /* NCViewerPDFSearch.swift */,
@@ -2884,6 +2887,7 @@
 				F78ACD4621903D010088454D /* NCGridCell.xib in Resources */,
 				F73B4EF21F470D9100BBEE4B /* EUCTWFreq.tab in Resources */,
 				F769453C22E9CFFF000A798A /* NCShareUserCell.xib in Resources */,
+				F76D3CF52428D0C1005DFA87 /* NCViewerPDF.storyboard in Resources */,
 				F700222C1EC479840080073F /* Custom.xcassets in Resources */,
 				F710D20024057E5E00A6033D /* NCActionSheetHeaderView.xib in Resources */,
 				F758B45A212C564000515F55 /* Scan.storyboard in Resources */,

+ 1 - 0
iOSClient/CCGlobal.h

@@ -335,6 +335,7 @@
 #define k_notificationCenter_renameFile                 @"renameFile"
 #define k_notificationCenter_moveFile                   @"moveFile"
 
+#define k_notificationCenter_menuSearchTextPDF          @"menuSearchTextPDF"
 
 // -----------------------------------------------------------------------------------------------------------
 // INTERNAL

+ 12 - 0
iOSClient/Main/Menu/NCDetailNavigationController+Menu.swift

@@ -69,6 +69,16 @@ extension NCDetailNavigationController {
             )
         )
         
+        // PDF
+        
+        if #available(iOS 11.0, *) {
+            if (metadata.typeFile == k_metadataTypeFile_document && metadata.contentType == "application/pdf" ) {
+                NotificationCenter.default.post(name: Notification.Name.init(rawValue: k_notificationCenter_menuSearchTextPDF), object: nil)
+            }
+        }
+        
+        // IMAGE - VIDEO - AUDIO
+        
         if (metadata.typeFile == k_metadataTypeFile_image || metadata.typeFile == k_metadataTypeFile_video || metadata.typeFile == k_metadataTypeFile_audio) && !CCUtility.fileProviderStorageExists(appDelegate.activeDetail.metadata?.ocId, fileNameView: appDelegate.activeDetail.metadata?.fileNameView) && metadata.session == "" && metadata.typeFile == k_metadataTypeFile_image {
             actions.append(
                 NCMenuAction(title: NSLocalizedString("_download_image_max_", comment: ""),
@@ -113,6 +123,8 @@ extension NCDetailNavigationController {
             )
         }
         
+        // CLOSE
+        
         actions.append(
             NCMenuAction(title: NSLocalizedString("_close_", comment: ""),
                 icon: CCGraphics.changeThemingColorImage(UIImage(named: "exit"), width: 50, height: 50, color: NCBrandColor.sharedInstance.icon),

+ 437 - 0
iOSClient/Viewer/NCViewerImage/NCViewerImage.swift

@@ -0,0 +1,437 @@
+//
+//  NCViewerImage.swift
+//  Nextcloud
+//
+//  Created by Marino Faggiana on 18/02/20.
+//  Copyright © 2020 Marino Faggiana. All rights reserved.
+//
+//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+import Foundation
+import NCCommunication
+
+class NCViewerImage: NSObject {
+
+    private weak var metadata: tableMetadata?
+    private let appDelegate = UIApplication.shared.delegate as! AppDelegate
+    
+    private var viewerImageViewController: NCViewerImageViewController?
+    private var metadatas = [tableMetadata]()
+      
+    private var favoriteFilterImage: Bool = false
+    private var mediaFilterImage: Bool = false
+    private var offlineFilterImage: Bool = false
+
+    private weak var detailViewController: NCDetailViewController?
+    
+    private var videoLayer: AVPlayerLayer?
+    private var viewerImageViewControllerLongPressInProgress = false
+
+    init(_ metadata: tableMetadata, metadatas: [tableMetadata], detailViewController: NCDetailViewController, favoriteFilterImage: Bool, mediaFilterImage: Bool, offlineFilterImage: Bool) {
+        
+        self.metadata = metadata
+        self.metadatas = metadatas
+        self.detailViewController = detailViewController
+        self.favoriteFilterImage = favoriteFilterImage
+        self.mediaFilterImage = mediaFilterImage
+        self.offlineFilterImage = offlineFilterImage
+    }
+        
+    func viewImage() {
+        guard let detailViewController = detailViewController else { return }
+        viewerImageViewController = NCViewerImageViewController(index: 0, dataSource: self, delegate: self)
+                
+        for view in detailViewController.backgroundView.subviews { view.removeFromSuperview() }
+        
+        if let metadatas = NCViewerImageCommon.shared.getMetadatasDatasource(metadata: self.metadata, metadatas: self.metadatas, favoriteDatasorce: favoriteFilterImage, mediaDatasorce: mediaFilterImage, offLineDatasource: offlineFilterImage) {
+                            
+            var index = 0
+            if let indexFound = metadatas.firstIndex(where: { $0.ocId == self.metadata?.ocId }) { index = indexFound }
+            self.metadatas = metadatas
+            
+            self.viewerImageViewController = NCViewerImageViewController(index: index, dataSource: self, delegate: self)
+            if viewerImageViewController != nil {
+                           
+                detailViewController.backgroundView.image = nil
+
+                viewerImageViewController!.view.isHidden = true
+                
+                viewerImageViewController!.enableInteractiveDismissal = true
+                
+                detailViewController.addChild(viewerImageViewController!)
+                detailViewController.backgroundView.addSubview(viewerImageViewController!.view)
+                
+                viewerImageViewController!.view.frame = CGRect(x: 0, y: 0, width: detailViewController.backgroundView.frame.width, height: detailViewController.backgroundView.frame.height)
+                viewerImageViewController!.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+                
+                viewerImageViewController!.didMove(toParent: detailViewController)
+                
+                DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
+                    self.viewerImageViewController!.changeInViewSize(to: detailViewController.backgroundView.frame.size)
+                    self.viewerImageViewController!.view.isHidden = false
+                }
+            }
+        }
+        
+        NotificationCenter.default.addObserver(self, selector: #selector(self.synchronizationMedia(_:)), name: NSNotification.Name(rawValue: k_notificationCenter_synchronizationMedia), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(self.changeDisplayMode), name: NSNotification.Name(rawValue: k_notificationCenter_splitViewChangeDisplayMode), object: nil)
+        
+        NotificationCenter.default.addObserver(self, selector: #selector(self.downloadFile(_:)), name: NSNotification.Name(rawValue: k_notificationCenter_downloadFile), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(self.deleteFile(_:)), name: NSNotification.Name(rawValue: k_notificationCenter_deleteFile), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(self.renameFile(_:)), name: NSNotification.Name(rawValue: k_notificationCenter_renameFile), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(self.moveFile(_:)), name: NSNotification.Name(rawValue: k_notificationCenter_moveFile), object: nil)
+    }
+    
+    //MARK: - NotificationCenter
+
+    @objc func changeDisplayMode() {
+        guard let detailViewController = detailViewController else { return }
+        NCViewerImageCommon.shared.imageChangeSizeView(viewerImageViewController: viewerImageViewController, size: detailViewController.backgroundView.frame.size, metadata: metadata)
+    }
+    
+    @objc func synchronizationMedia(_ notification: NSNotification) {
+        if let userInfo = notification.userInfo as NSDictionary? {
+            if let type = userInfo["type"] as? String {
+                
+                if self.mediaFilterImage {
+                    
+                    if let metadatas = appDelegate.activeMedia.sectionDatasource.metadatas as? [tableMetadata] {
+                        self.metadatas = metadatas
+                    }
+                    
+                    if type == "delete" {
+                        if metadatas.count > 0 {
+                            var index = viewerImageViewController!.index - 1
+                            if index < 0 { index = 0}
+                            self.metadata = metadatas[index]
+                            viewImage()
+                            
+                        } else {
+                            
+                            detailViewController?.viewUnload()
+                        }
+                    }
+                    
+                    if type == "rename" || type == "move"   {
+                        viewerImageViewController?.reloadContentViews()
+                    }
+                }
+            }
+        }
+    }
+
+    @objc func moveFile(_ notification: NSNotification) {
+        deleteFile(notification)
+    }
+    
+    @objc func deleteFile(_ notification: NSNotification) {
+        if let userInfo = notification.userInfo as NSDictionary? {
+            if let metadata = userInfo["metadata"] as? tableMetadata, let errorCode = userInfo["errorCode"] as? Int {
+                if errorCode != 0 || metadata.account != self.metadata?.account || metadata.serverUrl != self.metadata?.serverUrl { return }
+                
+                if !mediaFilterImage {
+                    if let metadatas = NCViewerImageCommon.shared.getMetadatasDatasource(metadata: self.metadata, metadatas: self.metadatas, favoriteDatasorce: favoriteFilterImage, mediaDatasorce: mediaFilterImage, offLineDatasource: offlineFilterImage) {
+                        var index = viewerImageViewController!.index - 1
+                        if index < 0 { index = 0}
+                        self.metadata = metadatas[index]
+                        viewImage()
+                    } else {
+                        detailViewController?.viewUnload()
+                    }
+                }
+            }
+        }
+    }
+    
+    @objc func renameFile(_ notification: NSNotification) {
+        if let userInfo = notification.userInfo as NSDictionary? {
+            if let metadata = userInfo["metadata"] as? tableMetadata, let errorCode = userInfo["errorCode"] as? Int {
+                if errorCode != 0 || metadata.account != self.metadata?.account || metadata.serverUrl != self.metadata?.serverUrl { return }
+                
+                if !mediaFilterImage {
+                    
+                    if NCViewerImageCommon.shared.getMetadatasDatasource(metadata: self.metadata, metadatas: self.metadatas, favoriteDatasorce: favoriteFilterImage, mediaDatasorce: mediaFilterImage, offLineDatasource: offlineFilterImage) != nil {
+                        viewImage()
+                    } else {
+                        detailViewController?.viewUnload()
+                    }
+                }
+            }
+        }
+    }
+    
+    @objc func downloadFile(_ notification: NSNotification) {
+        if let userInfo = notification.userInfo as NSDictionary? {
+            if let metadata = userInfo["metadata"] as? tableMetadata, let errorCode = userInfo["errorCode"] as? Int {
+                if metadata.account != self.metadata?.account || metadata.serverUrl != self.metadata?.serverUrl { return }
+                                
+                if  errorCode == 0  {
+                    viewerImageViewController?.reloadContentViews()
+                }
+            }
+        }
+    }
+}
+
+//MARK: - viewerImageViewController - Delegate/DataSource
+
+extension NCViewerImage: NCViewerImageViewControllerDelegate, NCViewerImageViewControllerDataSource {
+    
+    func numberOfItems(in viewerImageViewController: NCViewerImageViewController) -> Int {
+        return metadatas.count
+    }
+
+    func viewerImageViewController(_ viewerImageViewController: NCViewerImageViewController, imageAt index: Int, completion: @escaping (_ index: Int, _ image: UIImage?, _ metadata: tableMetadata, _ zoomScale: ZoomScale?, _ error: Error?) -> Void) {
+        
+        if index >= metadatas.count { return }
+        guard let detailViewController = detailViewController else { return }
+
+        let metadata = metadatas[index]
+        let isPreview = CCUtility.fileProviderStorageIconExists(metadata.ocId, fileNameView: metadata.fileNameView)
+        let isImage = CCUtility.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView) > 0
+        
+        // Refresh self metadata && title
+        if viewerImageViewController.index < metadatas.count {
+            self.metadata = metadatas[viewerImageViewController.index]
+            detailViewController.metadata = self.metadata
+            detailViewController.navigationController?.navigationBar.topItem?.title = self.metadata!.fileNameView
+            
+        }
+        
+        // Status Current
+        if index == viewerImageViewController.currentItemIndex {
+            statusViewImage(metadata: metadata, viewerImageViewController: viewerImageViewController)
+        }
+        
+        // Preview for Video
+        if metadata.typeFile == k_metadataTypeFile_video && !isPreview && isImage {
+            
+            CCGraphics.createNewImage(from: metadata.fileNameView, ocId: metadata.ocId, filterGrayScale: false, typeFile: metadata.typeFile, writeImage: true)
+        }
+        
+        // Original only for actual
+        if metadata.typeFile == k_metadataTypeFile_image && isImage && index == viewerImageViewController.index {
+                
+            if let image = NCViewerImageCommon.shared.getImage(metadata: metadata) {
+                completion(index, image, metadata, ZoomScale.default, nil)
+            } else {
+                completion(index, NCViewerImageCommon.shared.getImageOffOutline(frame: detailViewController.view.frame, type: metadata.typeFile), metadata, ZoomScale.default, nil)
+            }
+                
+        // HEIC
+        } else if metadata.contentType == "image/heic" && CCUtility.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView) == 0 && metadata.hasPreview == false {
+            
+            let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+            let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)!
+            
+            _ = NCCommunication.sharedInstance.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: metadata.account, progressHandler: { (progress) in }) { (account, etag, date, length, errorCode, errorDescription) in
+                                
+                if errorCode == 0 && account == metadata.account {
+                    
+                    _ = NCManageDatabase.sharedInstance.addLocalFile(metadata: metadata)
+                    
+                    if let image = UIImage.init(contentsOfFile: fileNameLocalPath) {
+                        
+                        CCGraphics.createNewImage(from: metadata.fileNameView, ocId: metadata.ocId, filterGrayScale: false, typeFile: metadata.typeFile, writeImage: true)
+                        
+                        completion(index, image, metadata, ZoomScale.default, nil)
+                    } else {
+                        completion(index, NCViewerImageCommon.shared.getImageOffOutline(frame: detailViewController.view.frame, type: metadata.typeFile), metadata, ZoomScale.default, nil)
+                    }
+                } else if errorCode != 0 {
+                    completion(index, NCViewerImageCommon.shared.getImageOffOutline(frame: detailViewController.view.frame, type: metadata.typeFile), metadata, ZoomScale.default, nil)
+                }
+            }
+        
+        // Preview
+        } else if isPreview {
+                
+            if let image = NCViewerImageCommon.shared.getThumbnailImage(metadata: metadata) {
+                completion(index, image, metadata, ZoomScale.default, nil)
+            } else {
+                completion(index, NCViewerImageCommon.shared.getImageOffOutline(frame: detailViewController.view.frame, type: metadata.typeFile), metadata, ZoomScale.default, nil)
+            }
+    
+        } else if metadata.hasPreview {
+                
+            let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, activeUrl: appDelegate.activeUrl)!
+            let fileNameLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, fileNameView: metadata.fileNameView)!
+                    
+            NCCommunication.sharedInstance.downloadPreview(serverUrl: appDelegate.activeUrl, fileNamePath: fileNamePath, fileNameLocalPath: fileNameLocalPath, width: CGFloat(k_sizePreview), height: CGFloat(k_sizePreview), account: metadata.account) { (account, data, errorCode, errorMessage) in
+                if errorCode == 0 && data != nil {
+                    completion(index, UIImage.init(data: data!), metadata, ZoomScale.default, nil)
+                } else {
+                    completion(index, NCViewerImageCommon.shared.getImageOffOutline(frame: detailViewController.view.frame, type: metadata.typeFile), metadata, ZoomScale.default, nil)
+                }
+            }
+        } else {
+            completion(index, NCViewerImageCommon.shared.getImageOffOutline(frame: detailViewController.view.frame, type: metadata.typeFile), metadata, ZoomScale.default, nil)
+        }
+    }
+    
+    func viewerImageViewController(_ viewerImageViewController: NCViewerImageViewController, didChangeFocusTo index: Int, view: NCViewerImageContentView, metadata: tableMetadata) {
+                
+        if metadata.typeFile == k_metadataTypeFile_image {
+            DispatchQueue.global().async {
+                if let image = NCViewerImageCommon.shared.getImage(metadata: metadata) {
+                    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400)) {
+                        view.image = image
+                    }
+                }
+            }
+        }
+        
+        statusViewImage(metadata: metadata, viewerImageViewController: viewerImageViewController)
+    }
+    
+    func viewerImageViewControllerTap(_ viewerImageViewController: NCViewerImageViewController, metadata: tableMetadata) {
+        
+        guard let detailViewController = detailViewController else { return }
+        guard let navigationController = detailViewController.navigationController else { return }
+        
+        if metadata.typeFile == k_metadataTypeFile_image {
+        
+            if navigationController.isNavigationBarHidden {
+                detailViewController.navigateControllerBarHidden(false)
+                viewerImageViewController.statusView.isHidden = false
+            } else {
+                detailViewController.navigateControllerBarHidden(true)
+                viewerImageViewController.statusView.isHidden = true
+            }
+            
+            NCViewerImageCommon.shared.imageChangeSizeView(viewerImageViewController: viewerImageViewController, size: detailViewController.backgroundView.frame.size, metadata: metadata)
+            
+        } else {
+            
+            if let viewerImageVideo = UIStoryboard(name: "NCViewerImageVideo", bundle: nil).instantiateInitialViewController() as? NCViewerImageVideo {
+                viewerImageVideo.metadata = metadata
+                detailViewController.present(viewerImageVideo, animated: false) { }
+            }
+        }
+        
+        statusViewImage(metadata: metadata, viewerImageViewController: viewerImageViewController)
+    }
+    
+    func viewerImageViewControllerLongPressBegan(_ viewerImageViewController: NCViewerImageViewController, metadata: tableMetadata) {
+        
+        viewerImageViewControllerLongPressInProgress = true
+        
+        let fileName = (metadata.fileNameView as NSString).deletingPathExtension + ".mov"
+        if let metadata = NCManageDatabase.sharedInstance.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView LIKE[c] %@", metadata.account, metadata.serverUrl, fileName)) {
+            
+            if CCUtility.fileProviderStorageSize(metadata.ocId, fileNameView: metadata.fileNameView) > 0 {
+                
+                viewMOV(viewerImageViewController: viewerImageViewController, metadata: metadata)
+                
+            } else {
+                
+                let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
+                let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)!
+                                
+                _ = NCCommunication.sharedInstance.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: metadata.account, progressHandler: { (progress) in
+                                                        
+                }) { (account, etag, date, length, errorCode, errorDescription) in
+                                        
+                    if errorCode == 0 && account == metadata.account {
+                        
+                        _ = NCManageDatabase.sharedInstance.addLocalFile(metadata: metadata)
+                        self.viewMOV(viewerImageViewController: viewerImageViewController, metadata: metadata)
+                    }
+                }
+            }
+        }
+    }
+    
+    func viewerImageViewControllerLongPressEnded(_ viewerImageViewController: NCViewerImageViewController, metadata: tableMetadata) {
+        
+        viewerImageViewControllerLongPressInProgress = false
+        
+        appDelegate.player?.pause()
+        videoLayer?.removeFromSuperlayer()
+    }
+    
+    func viewerImageViewControllerDismiss() {
+        detailViewController?.viewUnload()
+    }
+    
+    @objc func downloadImage() {
+        
+        guard let metadata = self.metadata else {return }
+        
+        metadata.session = k_download_session
+        metadata.sessionError = ""
+        metadata.sessionSelector = ""
+        metadata.status = Int(k_metadataStatusWaitDownload)
+        
+        self.metadata = NCManageDatabase.sharedInstance.addMetadata(metadata)
+        
+        if let index = metadatas.firstIndex(where: { $0.ocId == metadata.ocId }) {
+            metadatas[index] = self.metadata!
+        }
+        
+        appDelegate.startLoadAutoDownloadUpload()
+    }
+    
+    func saveLivePhoto(metadata: tableMetadata, metadataMov: tableMetadata) {
+        
+        let fileNameImage = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!)
+        let fileNameMov = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadataMov.ocId, fileNameView: metadataMov.fileNameView)!)
+        
+        NCLivePhoto.generate(from: fileNameImage, videoURL: fileNameMov, progress: { progress in
+            self.detailViewController?.progress(Float(progress))
+        }, completion: { livePhoto, resources in
+            self.detailViewController?.progress(0)
+            if resources != nil {
+                NCLivePhoto.saveToLibrary(resources!) { (result) in
+                    if !result {
+                        NCContentPresenter.shared.messageNotification("_error_", description: "_livephoto_save_error_", delay: TimeInterval(k_dismissAfterSecond), type: NCContentPresenter.messageType.error, errorCode: Int(k_CCErrorInternalError))
+                    }
+                }
+            } else {
+                NCContentPresenter.shared.messageNotification("_error_", description: "_livephoto_save_error_", delay: TimeInterval(k_dismissAfterSecond), type: NCContentPresenter.messageType.error, errorCode: Int(k_CCErrorInternalError))
+            }
+        })
+    }
+    
+    func statusViewImage(metadata: tableMetadata, viewerImageViewController: NCViewerImageViewController) {
+        
+        var colorStatus: UIColor = UIColor.white.withAlphaComponent(0.8)
+        if detailViewController?.view.backgroundColor?.isLight() ?? true { colorStatus = UIColor.black.withAlphaComponent(0.8) }
+        
+        if NCUtility.sharedInstance.hasMOV(metadata: metadata) != nil {
+            viewerImageViewController.statusView.image = CCGraphics.changeThemingColorImage(UIImage.init(named: "livePhoto"), width: 100, height: 100, color: colorStatus)
+        } else if metadata.typeFile == k_metadataTypeFile_video || metadata.typeFile == k_metadataTypeFile_audio {
+            viewerImageViewController.statusView.image = CCGraphics.changeThemingColorImage(UIImage.init(named: "play"), width: 100, height: 100, color: colorStatus)
+        } else {
+            viewerImageViewController.statusView.image = nil
+        }
+    }
+    
+    func viewMOV(viewerImageViewController: NCViewerImageViewController, metadata: tableMetadata) {
+        
+        if !viewerImageViewControllerLongPressInProgress { return }
+        
+        appDelegate.player = AVPlayer(url: URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!))
+        videoLayer = AVPlayerLayer(player: appDelegate.player)
+        if  videoLayer != nil {
+            videoLayer!.frame = viewerImageViewController.view.frame
+            videoLayer!.videoGravity = AVLayerVideoGravity.resizeAspect
+            viewerImageViewController.view.layer.addSublayer(videoLayer!)
+            appDelegate.player?.play()
+        }
+    }
+}

+ 213 - 0
iOSClient/Viewer/NCViewerPDF/NCViewerPDF.storyboard

@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina5_5" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Outline-->
+        <scene sceneID="VBD-iF-aTd">
+            <objects>
+                <tableViewController storyboardIdentifier="OutlineTableVC" id="v4c-4C-sCZ" sceneMemberID="viewController">
+                    <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="4TC-I7-6Vw">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                        <prototypes>
+                            <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="Bqa-Tg-cvh">
+                                <rect key="frame" x="0.0" y="28" width="414" height="43.666667938232422"/>
+                                <autoresizingMask key="autoresizingMask"/>
+                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Bqa-Tg-cvh" id="2vM-RA-TYX">
+                                    <rect key="frame" x="0.0" y="0.0" width="414" height="43.666667938232422"/>
+                                    <autoresizingMask key="autoresizingMask"/>
+                                </tableViewCellContentView>
+                            </tableViewCell>
+                        </prototypes>
+                        <connections>
+                            <outlet property="dataSource" destination="v4c-4C-sCZ" id="Ss5-RG-wd2"/>
+                            <outlet property="delegate" destination="v4c-4C-sCZ" id="fBx-60-64W"/>
+                        </connections>
+                    </tableView>
+                    <navigationItem key="navigationItem" title="Outline" id="sLs-6f-3D1">
+                        <barButtonItem key="rightBarButtonItem" title="Cancel" id="xjM-6J-KNZ">
+                            <connections>
+                                <action selector="cancelAction:" destination="v4c-4C-sCZ" id="QMZ-SQ-b1h"/>
+                            </connections>
+                        </barButtonItem>
+                    </navigationItem>
+                </tableViewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="v1u-om-Ku6" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="25" y="796"/>
+        </scene>
+        <!--Navigation Controller-->
+        <scene sceneID="nAa-JU-r5L">
+            <objects>
+                <navigationController storyboardIdentifier="OutlineNavVC" automaticallyAdjustsScrollViewInsets="NO" id="32m-jB-tjS" sceneMemberID="viewController">
+                    <toolbarItems/>
+                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="ac3-AS-2ad">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                    </navigationBar>
+                    <nil name="viewControllers"/>
+                    <connections>
+                        <segue destination="v4c-4C-sCZ" kind="relationship" relationship="rootViewController" id="6qD-Je-rHo"/>
+                    </connections>
+                </navigationController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="I9e-qD-yrc" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="-715" y="796"/>
+        </scene>
+        <!--Search-->
+        <scene sceneID="RUj-UC-ySq">
+            <objects>
+                <tableViewController storyboardIdentifier="NCViewerPDFSearch" id="pCE-Md-Nnq" customClass="NCViewerPDFSearch" customModule="Nextcloud" customModuleProvider="target" sceneMemberID="viewController">
+                    <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="tGC-IJ-dLf">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                        <prototypes>
+                            <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="tPH-Ua-1BG">
+                                <rect key="frame" x="0.0" y="28" width="414" height="43.666667938232422"/>
+                                <autoresizingMask key="autoresizingMask"/>
+                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="tPH-Ua-1BG" id="we5-RX-NY9">
+                                    <rect key="frame" x="0.0" y="0.0" width="414" height="43.666667938232422"/>
+                                    <autoresizingMask key="autoresizingMask"/>
+                                </tableViewCellContentView>
+                            </tableViewCell>
+                        </prototypes>
+                        <connections>
+                            <outlet property="dataSource" destination="pCE-Md-Nnq" id="1Lm-lc-tES"/>
+                            <outlet property="delegate" destination="pCE-Md-Nnq" id="O3N-L3-AY0"/>
+                        </connections>
+                    </tableView>
+                    <navigationItem key="navigationItem" title="Search" id="QGL-AL-VBj"/>
+                </tableViewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="yFG-hW-V7N" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="26" y="1532"/>
+        </scene>
+        <!--Navigation Controller-->
+        <scene sceneID="rmL-pT-QPn">
+            <objects>
+                <navigationController storyboardIdentifier="SearchNavVC" automaticallyAdjustsScrollViewInsets="NO" id="lY0-BD-LiX" sceneMemberID="viewController">
+                    <toolbarItems/>
+                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="thw-BA-9TV">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                    </navigationBar>
+                    <nil name="viewControllers"/>
+                    <connections>
+                        <segue destination="pCE-Md-Nnq" kind="relationship" relationship="rootViewController" id="mvL-8q-KRe"/>
+                    </connections>
+                </navigationController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="h1q-cz-AFa" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="-715" y="1532"/>
+        </scene>
+        <!--BookmarkViewController-->
+        <scene sceneID="c1d-hB-JBp">
+            <objects>
+                <viewController storyboardIdentifier="BookmarkViewController" title="BookmarkViewController" id="xkh-Id-yNA" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="hXB-ZK-36O">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="f5i-f0-WNa" userLabel="TableView">
+                                <rect key="frame" x="0.0" y="44" width="414" height="692"/>
+                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                <prototypes>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="BookmarkTableViewCell" id="VH4-vI-xNm" customClass="BookmarkTableViewCell">
+                                        <rect key="frame" x="0.0" y="28" width="414" height="44"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VH4-vI-xNm" id="HUU-Gn-wiI">
+                                            <rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="hfq-Tc-7hJ">
+                                                    <rect key="frame" x="16" y="13" width="390" height="18"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                                                    <textInputTraits key="textInputTraits"/>
+                                                    <connections>
+                                                        <action selector="textFieldEditingChanged:" destination="xkh-Id-yNA" eventType="editingChanged" id="CPF-Yo-hZX"/>
+                                                        <outlet property="delegate" destination="xkh-Id-yNA" id="CFE-eJ-vvI"/>
+                                                    </connections>
+                                                </textField>
+                                            </subviews>
+                                            <constraints>
+                                                <constraint firstAttribute="trailing" secondItem="hfq-Tc-7hJ" secondAttribute="trailing" constant="8" id="m1u-0a-q01"/>
+                                                <constraint firstItem="hfq-Tc-7hJ" firstAttribute="leading" secondItem="HUU-Gn-wiI" secondAttribute="leading" constant="16" id="oJ6-JZ-eqP"/>
+                                                <constraint firstItem="hfq-Tc-7hJ" firstAttribute="centerY" secondItem="HUU-Gn-wiI" secondAttribute="centerY" id="rHD-xk-soD"/>
+                                            </constraints>
+                                        </tableViewCellContentView>
+                                        <connections>
+                                            <outlet property="bookmarkTextField" destination="hfq-Tc-7hJ" id="spb-p4-g6I"/>
+                                        </connections>
+                                    </tableViewCell>
+                                </prototypes>
+                                <connections>
+                                    <outlet property="dataSource" destination="xkh-Id-yNA" id="B73-74-QaN"/>
+                                    <outlet property="delegate" destination="xkh-Id-yNA" id="0fd-Ye-wv8"/>
+                                </connections>
+                            </tableView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="f5i-f0-WNa" firstAttribute="bottom" secondItem="4lC-oa-Knl" secondAttribute="bottom" id="fAr-vt-4tA"/>
+                            <constraint firstItem="f5i-f0-WNa" firstAttribute="trailing" secondItem="4lC-oa-Knl" secondAttribute="trailing" id="lw0-uD-44u"/>
+                            <constraint firstItem="f5i-f0-WNa" firstAttribute="leading" secondItem="4lC-oa-Knl" secondAttribute="leading" id="m2O-MC-LYG"/>
+                            <constraint firstItem="f5i-f0-WNa" firstAttribute="top" secondItem="4lC-oa-Knl" secondAttribute="top" id="w8C-WY-ghM"/>
+                        </constraints>
+                        <viewLayoutGuide key="safeArea" id="4lC-oa-Knl"/>
+                    </view>
+                    <navigationItem key="navigationItem" title="Bookmarks" id="ouz-6J-exJ">
+                        <leftBarButtonItems>
+                            <barButtonItem systemItem="add" id="sFi-ot-qG5">
+                                <connections>
+                                    <action selector="addBookmarkAction:" destination="xkh-Id-yNA" id="vn3-FF-DBK"/>
+                                </connections>
+                            </barButtonItem>
+                            <barButtonItem title="Edit" id="i7T-xe-Djd">
+                                <connections>
+                                    <action selector="editingToogleActon:" destination="xkh-Id-yNA" id="yPJ-9W-Egn"/>
+                                </connections>
+                            </barButtonItem>
+                        </leftBarButtonItems>
+                        <barButtonItem key="rightBarButtonItem" title="Cancel" id="Vff-PJ-8SD">
+                            <connections>
+                                <action selector="cancelAction:" destination="xkh-Id-yNA" id="E2e-Jx-qlV"/>
+                            </connections>
+                        </barButtonItem>
+                    </navigationItem>
+                    <connections>
+                        <outlet property="editButton" destination="i7T-xe-Djd" id="j8o-yh-zXa"/>
+                        <outlet property="tableView" destination="f5i-f0-WNa" id="wkx-cf-WDF"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="Idr-83-KJH" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="25" y="2308"/>
+        </scene>
+        <!--Navigation Controller-->
+        <scene sceneID="pQb-Fu-5Yq">
+            <objects>
+                <navigationController automaticallyAdjustsScrollViewInsets="NO" id="Mew-Xj-qi5" sceneMemberID="viewController">
+                    <toolbarItems/>
+                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="eRU-A6-ZFR">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                    </navigationBar>
+                    <nil name="viewControllers"/>
+                    <connections>
+                        <segue destination="xkh-Id-yNA" kind="relationship" relationship="rootViewController" id="ht5-FA-WuC"/>
+                    </connections>
+                </navigationController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="wp2-Id-ODl" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="-716" y="2308"/>
+        </scene>
+    </scenes>
+</document>

+ 27 - 3
iOSClient/Viewer/NCViewerPDF/NCViewerPDF.swift

@@ -26,12 +26,13 @@ import PDFKit
 
 @available(iOS 11, *)
 
-@objc class NCViewerPDF: PDFView {
+@objc class NCViewerPDF: PDFView, NCViewerPDFSearchDelegate {
     
     private let appDelegate = UIApplication.shared.delegate as! AppDelegate
     private var thumbnailViewHeight: CGFloat = 40
     private var pdfThumbnailView: PDFThumbnailView?
-
+    private var pdfDocument: PDFDocument?
+    
     required init?(coder: NSCoder) {
         super.init(coder: coder)
     }
@@ -42,6 +43,8 @@ import PDFKit
         super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: height))
         
         NotificationCenter.default.addObserver(self, selector: #selector(self.changeTheming), name: NSNotification.Name(rawValue: k_notificationCenter_changeTheming), object: nil)
+        
+        NotificationCenter.default.addObserver(self, selector: #selector(self.searchText), name: NSNotification.Name(rawValue: k_notificationCenter_menuSearchTextPDF), object: nil)
     }
     
     @objc func changeTheming() {
@@ -53,7 +56,7 @@ import PDFKit
     }
     
     @objc func setupPdfView(filePath: URL, view: UIView) {
-        guard let pdfDocument = PDFDocument(url: filePath) else { return }
+        pdfDocument = PDFDocument(url: filePath)
         
         document = pdfDocument
         backgroundColor = NCBrandColor.sharedInstance.backgroundView
@@ -115,4 +118,25 @@ import PDFKit
              
         self.frame = CGRect(x: 0, y: 0, width: size.width, height: height)
     }
+    
+    @objc func searchText() {
+        
+        let viewerPDFSearch = UIStoryboard.init(name: "NCViewerPDF", bundle: nil).instantiateViewController(withIdentifier: "NCViewerPDFSearch") as! NCViewerPDFSearch
+        viewerPDFSearch.delegate = self
+        viewerPDFSearch.pdfDocument = pdfDocument
+        
+        let navigaionController = UINavigationController.init(rootViewController: viewerPDFSearch)
+        
+//        if traitCollection.horizontalSizeClass == .regular {
+            
+//        } else {
+            appDelegate.activeDetail.present(navigaionController, animated: true)
+//        }
+    }
+    
+    func searchPdfSelection(_ pdfSelection: PDFSelection) {
+        pdfSelection.color = .yellow
+        currentSelection = pdfSelection
+        go(to: pdfSelection)
+    }
 }