diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index b285c91715..6414ed2d6e 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -851,6 +851,7 @@ F7D56B1A2972405500FA46C4 /* Mantis in Frameworks */ = {isa = PBXBuildFile; productRef = F7D56B192972405500FA46C4 /* Mantis */; }; F7D57C8626317BDA00DE301D /* NCAccountRequest.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7CA212C25F1333200826ABB /* NCAccountRequest.storyboard */; }; F7D57C8B26317BDE00DE301D /* NCAccountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7CA212B25F1333200826ABB /* NCAccountRequest.swift */; }; + F7D60CAF2C941ACB008FBFDD /* NCMediaPinchGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7D60CAE2C941ACB008FBFDD /* NCMediaPinchGesture.swift */; }; F7D68FCC28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7D68FCB28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift */; }; F7D68FCD28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7D68FCB28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift */; }; F7D68FCE28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7D68FCB28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift */; }; @@ -1696,6 +1697,7 @@ F7D532541F5D4155006568B1 /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; F7D5328F1F5D443B006568B1 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; F7D532A41F5D4461006568B1 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + F7D60CAE2C941ACB008FBFDD /* NCMediaPinchGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaPinchGesture.swift; sourceTree = ""; }; F7D68FCB28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+DashboardWidget.swift"; sourceTree = ""; }; F7D890742BD25C570050B8A6 /* NCCollectionViewCommon+DragDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+DragDrop.swift"; sourceTree = ""; }; F7D96FCB246ED7E100536D73 /* NCNetworkingCheckRemoteUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCNetworkingCheckRemoteUser.swift; sourceTree = ""; }; @@ -2899,6 +2901,7 @@ F7802B312BD5584F00D74270 /* NCMedia+DragDrop.swift */, F7BD0A032C4689E9003A4A6D /* NCMedia+MediaLayout.swift */, F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */, + F7D60CAE2C941ACB008FBFDD /* NCMediaPinchGesture.swift */, F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnail.swift */, F755CB3F2B8CB13C00CE27E9 /* NCMediaLayout.swift */, F741C2232B6B9FD600E849BB /* NCMediaSelectTabBar.swift */, @@ -4350,6 +4353,7 @@ F799DF852C4B7E56003410B5 /* NCSectionHeader.swift in Sources */, F78A10BF29322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */, F7743A122C33F0A20034F670 /* NCCollectionViewCommon+CollectionViewDelegate.swift in Sources */, + F7D60CAF2C941ACB008FBFDD /* NCMediaPinchGesture.swift in Sources */, F704B5E92430C0B800632F5F /* NCCreateFormUploadConflictCell.swift in Sources */, F7327E3D2B73B92800A462C7 /* NCNetworking+Synchronization.swift in Sources */, F72D404923D2082500A97FD0 /* NCViewerNextcloudText.swift in Sources */, @@ -5462,7 +5466,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 12; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5528,7 +5532,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 12; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; diff --git a/iOSClient/Media/NCMedia.swift b/iOSClient/Media/NCMedia.swift index 63fd3a498b..874eef6d7a 100644 --- a/iOSClient/Media/NCMedia.swift +++ b/iOSClient/Media/NCMedia.swift @@ -21,6 +21,7 @@ // along with this program. If not, see . // +import Foundation import UIKit import NextcloudKit import RealmSwift @@ -137,7 +138,7 @@ class NCMedia: UIViewController { self.reloadDataSource() } - let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))) + let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:))) collectionView.addGestureRecognizer(pinchGesture) NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil, queue: nil) { _ in @@ -159,7 +160,7 @@ class NCMedia: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(uploadedLivePhoto(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedLivePhoto), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(clear), name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(networkRemoveAll), name: UIApplication.didEnterBackgroundNotification, object: nil) } override func viewWillAppear(_ animated: Bool) { @@ -193,7 +194,7 @@ class NCMedia: UIViewController { NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) - clear() + networkRemoveAll() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -233,7 +234,7 @@ class NCMedia: UIViewController { // MARK: - NotificationCenter - @objc func clear() { + @objc func networkRemoveAll() { filesExists.removeAll() NCNetworking.shared.fileExistsQueue.cancelAll() NCNetworking.shared.downloadThumbnailQueue.cancelAll() @@ -343,67 +344,6 @@ class NCMedia: UIViewController { } } - @objc func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) { - func updateNumberOfColumns() { - let originalColumns = numberOfColumns - - if currentScale < 1 && numberOfColumns < maxColumns { - numberOfColumns += 1 - } else if currentScale > 1 && numberOfColumns > 1 { - numberOfColumns -= 1 - } - - if originalColumns != numberOfColumns { - - transitionColumns = true - self.collectionView.transform = .identity - self.currentScale = 1.0 - self.setTitleDate() - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.07) { - - self.collectionView.collectionViewLayout.invalidateLayout() - self.collectionView.reloadData() - - self.setTitleDate() - - if let layoutForView = self.database.getLayoutForView(account: self.session.account, key: NCGlobal.shared.layoutViewMedia, serverUrl: "") { - layoutForView.columnPhoto = self.numberOfColumns - self.database.setLayoutForView(layoutForView: layoutForView) - } - - self.transitionColumns = false - } - } - } - - switch gestureRecognizer.state { - case .began: - self.clear() - lastScale = gestureRecognizer.scale - case .changed: - guard !transitionColumns else { return } - let scale = gestureRecognizer.scale - let scaleChange = scale / lastScale - - currentScale *= scaleChange - currentScale = max(0.5, min(currentScale, 2.0)) - - updateNumberOfColumns() - - if numberOfColumns > 1 && numberOfColumns < maxColumns { - collectionView.transform = CGAffineTransform(scaleX: currentScale, y: currentScale) - } - - lastScale = scale - case .ended: - currentScale = 1.0 - collectionView.transform = .identity - default: - break - } - } - // MARK: - Image func getImage(metadata: NCMediaDataSource.Metadata, width: CGFloat? = nil) -> UIImage? { @@ -419,7 +359,7 @@ class NCMedia: UIViewController { } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) { returnImage = image } else if NCNetworking.shared.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnail)?.metadata.ocId == metadata.ocId }).isEmpty { - NCNetworking.shared.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnail(metadata: metadata, collectionView: self.collectionView, delegate: self)) + NCNetworking.shared.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnail(metadata: metadata, collectionView: self.collectionView, media: self)) } return returnImage diff --git a/iOSClient/Media/NCMediaDataSource.swift b/iOSClient/Media/NCMediaDataSource.swift index 92a68f85b4..f930c66479 100644 --- a/iOSClient/Media/NCMediaDataSource.swift +++ b/iOSClient/Media/NCMediaDataSource.swift @@ -44,6 +44,7 @@ extension NCMedia { self.lockQueue.sync { guard self.isViewActived, !self.hasRunSearchMedia, + !self.transitionColumns, !isEditMode, NCNetworking.shared.downloadThumbnailQueue.operationCount == 0, let tableAccount = database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) diff --git a/iOSClient/Media/NCMediaDownloadThumbnail.swift b/iOSClient/Media/NCMediaDownloadThumbnail.swift index 49bc37ff5c..1fd3a45edb 100644 --- a/iOSClient/Media/NCMediaDownloadThumbnail.swift +++ b/iOSClient/Media/NCMediaDownloadThumbnail.swift @@ -29,17 +29,16 @@ class NCMediaDownloadThumbnail: ConcurrentOperation { var metadata: NCMediaDataSource.Metadata var collectionView: UICollectionView? let utilityFileSystem = NCUtilityFileSystem() - let delegate: NCMedia? - var ext = "" + let media: NCMedia? + var width: CGFloat? - init(metadata: NCMediaDataSource.Metadata, collectionView: UICollectionView?, delegate: NCMedia?) { + init(metadata: NCMediaDataSource.Metadata, collectionView: UICollectionView?, media: NCMedia?) { self.metadata = metadata self.collectionView = collectionView - self.delegate = delegate + self.media = media - if let collectionView, let numberOfColumns = delegate?.numberOfColumns { - let width = collectionView.frame.size.width / CGFloat(numberOfColumns) - ext = NCGlobal.shared.getSizeExtension(width: width) + if let collectionView, let numberOfColumns = self.media?.numberOfColumns { + width = collectionView.frame.size.width / CGFloat(numberOfColumns) } } @@ -61,11 +60,11 @@ class NCMediaDownloadThumbnail: ConcurrentOperation { if let metadata = NCManageDatabase.shared.getMetadataFromOcId(self.metadata.ocId) { NCUtility().createImage(ocId: metadata.ocId, etag: metadata.etag, classFile: metadata.classFile, data: data) } + let image = self.media?.getImage(metadata: self.metadata, width: self.width) DispatchQueue.main.async { for case let cell as NCGridMediaCell in collectionView.visibleCells { - if cell.ocId == self.metadata.ocId, - let image = NCUtility().getImage(ocId: self.metadata.ocId, etag: self.metadata.etag, ext: self.ext) { + if cell.ocId == self.metadata.ocId { UIView.transition(with: cell.imageItem, duration: 0.75, options: .transitionCrossDissolve, diff --git a/iOSClient/Media/NCMediaLayout.swift b/iOSClient/Media/NCMediaLayout.swift index d84e4a2f0c..fccb724155 100644 --- a/iOSClient/Media/NCMediaLayout.swift +++ b/iOSClient/Media/NCMediaLayout.swift @@ -93,6 +93,8 @@ public class NCMediaLayout: UICollectionViewLayout { contentSize?.height = CGFloat(columnHeights[0]) return contentSize! } + public var frameWidth: Float = 0 + public var itemWidth: Float = 0 // MARK: - Private Properties private weak var delegate: NCMediaLayoutDelegate? { @@ -142,8 +144,8 @@ public class NCMediaLayout: UICollectionViewLayout { */ let minimumInteritemSpacing: Float = delegate.collectionView(collectionView, layout: self, minimumInteritemSpacingForSection: section) let sectionInset: UIEdgeInsets = delegate.collectionView(collectionView, layout: self, insetForSection: section) - let width = Float(collectionView.frame.size.width - sectionInset.left - sectionInset.right) - let itemWidth = ((width - Float(columnCount - 1) * Float(minimumColumnSpacing)) / Float(columnCount)) + frameWidth = Float(collectionView.frame.size.width - sectionInset.left - sectionInset.right) + itemWidth = ((frameWidth - Float(columnCount - 1) * Float(minimumColumnSpacing)) / Float(columnCount)) /* * 2. Section header diff --git a/iOSClient/Media/NCMediaPinchGesture.swift b/iOSClient/Media/NCMediaPinchGesture.swift new file mode 100644 index 0000000000..ac45f94350 --- /dev/null +++ b/iOSClient/Media/NCMediaPinchGesture.swift @@ -0,0 +1,89 @@ +// +// MediaZoom.swift +// Nextcloud +// +// Created by Marino Faggiana on 13/09/24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// 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 . +// + +import Foundation +import UIKit + +extension NCMedia { + @objc func handlePinchGesture(_ gestureRecognizer: UIPinchGestureRecognizer) { + func updateNumberOfColumns() { + let originalColumns = numberOfColumns + + if currentScale < 1 && numberOfColumns < maxColumns { + numberOfColumns += 1 + } else if currentScale > 1 && numberOfColumns > 1 { + numberOfColumns -= 1 + } + + if originalColumns != numberOfColumns { + + transitionColumns = true + self.collectionView.transform = .identity + self.currentScale = 1.0 + + UIView.transition(with: self.collectionView, duration: 0.20, options: .transitionCrossDissolve) { + self.collectionView.reloadData() + } completion: { _ in + self.setTitleDate() + + if let layoutForView = self.database.getLayoutForView(account: self.session.account, key: NCGlobal.shared.layoutViewMedia, serverUrl: "") { + layoutForView.columnPhoto = self.numberOfColumns + self.database.setLayoutForView(layoutForView: layoutForView) + } + } + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { + self.transitionColumns = false + } + } + + switch gestureRecognizer.state { + case .began: + networkRemoveAll() + lastScale = gestureRecognizer.scale + case .changed: + guard !transitionColumns else { + return + } + let scale = gestureRecognizer.scale + let scaleChange = scale / lastScale + + currentScale *= scaleChange + currentScale = max(0.5, min(currentScale, 2.0)) + + updateNumberOfColumns() + + if numberOfColumns > 1 && numberOfColumns < maxColumns { + collectionView.transform = CGAffineTransform(scaleX: currentScale, y: currentScale) + } + + lastScale = scale + case .ended: + currentScale = 1.0 + collectionView.transform = .identity + self.collectionView.reloadData() + default: + break + } + } +}