From 3a1e055fac258bd7d52a919b2887b7b9fe921861 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 28 Aug 2023 10:08:56 +0200 Subject: [PATCH 001/103] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 ++ iOSClient/Main/Main.storyboard | 20 ++++++--- iOSClient/Media/NCMediaNew.swift | 67 +++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 iOSClient/Media/NCMediaNew.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 489efa4387..51ac8f8bb2 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700510122DF63AC003A3356 /* NCShare.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F700510022DF63AC003A3356 /* NCShare.storyboard */; }; @@ -847,6 +848,7 @@ F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreUserCell.swift; sourceTree = ""; }; F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = ""; }; + F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaNew.swift; sourceTree = ""; }; F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = ""; }; F700510022DF63AC003A3356 /* NCShare.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShare.storyboard; sourceTree = ""; }; F700510222DF6897003A3356 /* Parchment.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Parchment.framework; path = Carthage/Build/iOS/Parchment.framework; sourceTree = ""; }; @@ -2371,6 +2373,7 @@ F7501C302212E57400FB1415 /* NCMedia.storyboard */, F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, + F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, ); path = Media; sourceTree = ""; @@ -3643,6 +3646,7 @@ F73B422C2476764F00A30FD3 /* NCNotification.swift in Sources */, 371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */, F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, + F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */, F79EDAA326B004980007D134 /* NCPlayerToolBar.swift in Sources */, F77444F8222816D5000D5EB0 /* NCPickerViewController.swift in Sources */, F77BB74A2899857B0090FC19 /* UINavigationController+Extension.swift in Sources */, diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index f6ed5e212b..3e3f022757 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -34,13 +34,13 @@ - + - + @@ -97,7 +97,7 @@ - + @@ -263,6 +263,16 @@ + + + + + + + + + + diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift new file mode 100644 index 0000000000..65be4e63b4 --- /dev/null +++ b/iOSClient/Media/NCMediaNew.swift @@ -0,0 +1,67 @@ +// +// NCMediaNew.swift +// Nextcloud +// +// Created by Milen on 25.08.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI +import PreviewSnapshots + +class NCMediaUIHostingController: UIHostingController { + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder, rootView: NCMediaNew()) + } +} + +struct NCMediaNew: View { + @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) + + var body: some View { + VStack { + ScrollView { + LazyVGrid(columns: gridColumns) { + ForEach(0...20, id: \.self) { value in + GeometryReader { geo in + ZStack(alignment: .topTrailing) { + // AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in + // image + // .resizable() + // .frame(width: CGFloat.random(in: 20...50), height: 50) + // } + AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in + image + .resizable() + .scaledToFill() + } placeholder: { + ProgressView() + } + .frame(width: CGFloat.random(in: 20...200), height: 50) + } + } + .cornerRadius(8.0) +// .aspectRatio(1, contentMode: .fit) + .aspectRatio(CGFloat.random(in: 1...5), contentMode: .fit) + } + } + } + } + } +} + +struct NCMediaNew_Previews: PreviewProvider { + static var previews: some View { + snapshots.previews.previewLayout(.sizeThatFits) + } + + static var snapshots: PreviewSnapshots { + PreviewSnapshots( + configurations: [ + .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") + ], + configure: { _ in + NCMediaNew() + }) + } +} From 7e8e0b7f8c60fbedc6390380e40518681bf815e9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 28 Aug 2023 16:43:39 +0200 Subject: [PATCH 002/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 65be4e63b4..d998413bb7 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -21,15 +21,9 @@ struct NCMediaNew: View { var body: some View { VStack { ScrollView { - LazyVGrid(columns: gridColumns) { + LazyVGrid(columns: gridColumns, alignment: .leading) { ForEach(0...20, id: \.self) { value in GeometryReader { geo in - ZStack(alignment: .topTrailing) { - // AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in - // image - // .resizable() - // .frame(width: CGFloat.random(in: 20...50), height: 50) - // } AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in image .resizable() @@ -38,11 +32,8 @@ struct NCMediaNew: View { ProgressView() } .frame(width: CGFloat.random(in: 20...200), height: 50) - } } .cornerRadius(8.0) -// .aspectRatio(1, contentMode: .fit) - .aspectRatio(CGFloat.random(in: 1...5), contentMode: .fit) } } } From 2a6cb225b42db55bbb19d2e7771f1bf499c1dbdd Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 28 Aug 2023 19:00:53 +0200 Subject: [PATCH 003/103] Make view model Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 17 ++++ iOSClient/Media/NCMediaNew.swift | 151 +++++++++++++++++++++++++--- 2 files changed, 155 insertions(+), 13 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 51ac8f8bb2..736e9a6c6b 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F3D4CC942A9CF2270015E283 /* ExyteGrid */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -1496,6 +1497,7 @@ F76DA969277B77EA0082465B /* DropDown in Frameworks */, F7F623B52A5EF4D30022D3D4 /* Gzip in Frameworks */, F75EAED826D2552E00F4320E /* MarqueeLabel in Frameworks */, + F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */, F710FC7A277B7D0000AA9FBF /* Realm in Frameworks */, F72DA9B425F53E4E00B87DB1 /* SwiftRichString in Frameworks */, F74E7720277A2EF40013B958 /* XLForm in Frameworks */, @@ -2885,6 +2887,7 @@ F7A1050D29E587AF00FFD92B /* TagListView */, F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, + F3D4CC942A9CF2270015E283 /* ExyteGrid */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3060,6 +3063,7 @@ F31F69622A2F929600162F76 /* XCRemoteSwiftPackageReference "swiftui-preview-snapshots" */, F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, + F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4904,6 +4908,14 @@ version = 1.4.0; }; }; + F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/exyte/Grid"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.5.0; + }; + }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; @@ -5196,6 +5208,11 @@ package = F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */; productName = SnapshotTestingHEIC; }; + F3D4CC942A9CF2270015E283 /* ExyteGrid */ = { + isa = XCSwiftPackageProductDependency; + package = F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */; + productName = ExyteGrid; + }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index d998413bb7..a64df9588b 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -8,6 +8,8 @@ import SwiftUI import PreviewSnapshots +import ExyteGrid +import NextcloudKit class NCMediaUIHostingController: UIHostingController { required init?(coder aDecoder: NSCoder) { @@ -16,29 +18,55 @@ class NCMediaUIHostingController: UIHostingController { } struct NCMediaNew: View { - @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) + @StateObject private var viewModel = NCMediaViewModel() + + @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50, maximum: .infinity)), count: 2) var body: some View { VStack { ScrollView { LazyVGrid(columns: gridColumns, alignment: .leading) { - ForEach(0...20, id: \.self) { value in - GeometryReader { geo in - AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in - image - .resizable() - .scaledToFill() - } placeholder: { - ProgressView() - } - .frame(width: CGFloat.random(in: 20...200), height: 50) - } - .cornerRadius(8.0) + ForEach(viewModel.metadatas, id: \.self) { metadata in +// GeometryReader { geo in + MediaCellView(metadata: metadata) +// .frame(width: CGFloat.random(in: 20...200), height: 50) +// } +// .cornerRadius(8.0) } } } } } +// var body: some View { +// Grid(0.. Void) { + guard let appDelegate, !appDelegate.account.isEmpty else { return } + + if account != appDelegate.account { + self.metadatas = [] + account = appDelegate.account +// DispatchQueue.main.async { self.collectionView?.reloadData() } + } + +// DispatchQueue.global().async { + self.queryDB(isForced: true) +// DispatchQueue.main.sync { +// self.reloadDataThenPerform { +// self.updateMediaControlVisibility() +// self.mediaCommandTitle() +// completion(self.metadatas) +// } +// } +// } + } + + func queryDB(isForced: Bool = false) { + guard let appDelegate else { return } + + livePhoto = CCUtility.getLivePhoto() + + if let activeAccount = NCManageDatabase.shared.getActiveAccount() { + self.mediaPath = activeAccount.mediaPath + } + + let startServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath + + predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue) + + if filterClassTypeImage { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) + } else if filterClassTypeVideo { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) + } else { + predicate = predicateDefault + } + + guard let predicate = predicate else { return } + + metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) + + switch CCUtility.getMediaSortDate() { + case "date": + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)} ) + case "creationDate": + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)} ) + case "uploadDate": + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)} ) + default: + break + } + } +} From 2abe5a8533d02f94da8eb76a07d2976b2cda9953 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 31 Aug 2023 13:44:59 +0200 Subject: [PATCH 004/103] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 22 +- iOSClient/Extensions/UIDevice+Extension.swift | 1 - iOSClient/Media/NCMediaNew.swift | 281 +++++++++++++++--- 3 files changed, 253 insertions(+), 51 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 736e9a6c6b..6622e753b2 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -116,6 +116,7 @@ F343A4BF2A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C12A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; + F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F34624522AA08C5700FAA7B1 /* FlowGrid */; }; F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8682A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8692A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; @@ -130,7 +131,6 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; - F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F3D4CC942A9CF2270015E283 /* ExyteGrid */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -1489,6 +1489,7 @@ F76DA966277B76F30082465B /* UICKeyChainStore in Frameworks */, F7792DE529EEE02D005930CE /* MobileVLCKit.xcframework in Frameworks */, F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */, + F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */, F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */, F76DA963277B760E0082465B /* Queuer in Frameworks */, F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, @@ -1497,7 +1498,6 @@ F76DA969277B77EA0082465B /* DropDown in Frameworks */, F7F623B52A5EF4D30022D3D4 /* Gzip in Frameworks */, F75EAED826D2552E00F4320E /* MarqueeLabel in Frameworks */, - F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */, F710FC7A277B7D0000AA9FBF /* Realm in Frameworks */, F72DA9B425F53E4E00B87DB1 /* SwiftRichString in Frameworks */, F74E7720277A2EF40013B958 /* XLForm in Frameworks */, @@ -2887,7 +2887,7 @@ F7A1050D29E587AF00FFD92B /* TagListView */, F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, - F3D4CC942A9CF2270015E283 /* ExyteGrid */, + F34624522AA08C5700FAA7B1 /* FlowGrid */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3063,7 +3063,7 @@ F31F69622A2F929600162F76 /* XCRemoteSwiftPackageReference "swiftui-preview-snapshots" */, F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, - F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */, + F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4908,12 +4908,12 @@ version = 1.4.0; }; }; - F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */ = { + F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/exyte/Grid"; + repositoryURL = "https://github.com/rdev/FlowGrid.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.5.0; + kind = exactVersion; + version = 1.0.0; }; }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { @@ -5208,10 +5208,10 @@ package = F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */; productName = SnapshotTestingHEIC; }; - F3D4CC942A9CF2270015E283 /* ExyteGrid */ = { + F34624522AA08C5700FAA7B1 /* FlowGrid */ = { isa = XCSwiftPackageProductDependency; - package = F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */; - productName = ExyteGrid; + package = F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */; + productName = FlowGrid; }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; diff --git a/iOSClient/Extensions/UIDevice+Extension.swift b/iOSClient/Extensions/UIDevice+Extension.swift index 77eaa26100..64e819ac6d 100644 --- a/iOSClient/Extensions/UIDevice+Extension.swift +++ b/iOSClient/Extensions/UIDevice+Extension.swift @@ -49,4 +49,3 @@ extension UIDeviceOrientation { } } } - diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index a64df9588b..c1d57ea8cf 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -8,8 +8,8 @@ import SwiftUI import PreviewSnapshots -import ExyteGrid import NextcloudKit +import FlowGrid class NCMediaUIHostingController: UIHostingController { required init?(coder aDecoder: NSCoder) { @@ -17,26 +17,84 @@ class NCMediaUIHostingController: UIHostingController { } } +// extension Array { +// func chunked(into size: Int) -> [[Element]] { +// return stride(from: 0, to: count, by: size).map { +// Array(self[$0 ..< Swift.min($0 + size, count)]) +// } +// } +// } + +struct MinHeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = max(value, nextValue()) + } +} + struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() - @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50, maximum: .infinity)), count: 2) + @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) + + @State private var minHeight: CGFloat = 0 var body: some View { VStack { +// StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in +// MediaCellView(metadata: metadata) +// }) +// FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in +// MediaCellView(metadata: metadata) +// } ScrollView { - LazyVGrid(columns: gridColumns, alignment: .leading) { - ForEach(viewModel.metadatas, id: \.self) { metadata in -// GeometryReader { geo in - MediaCellView(metadata: metadata) -// .frame(width: CGFloat.random(in: 20...200), height: 50) +// HStack(alignment: .top) { +// LazyVStack(spacing: 8) { +// ForEach(viewModel.metadatas.chunked(into: viewModel.metadatas.count / 2), id: \.self) { rowMetadatas in +// ForEach(rowMetadatas, id: \.self) { metadata in +// MediaCellView(metadata: metadata) +// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// } +// } +// } +// +// LazyVStack(spacing: 8) { +// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in +// ForEach(rowMetadatas, id: \.self) { metadata in +// MediaCellView(metadata: metadata) +// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// } +// } +// } +// } +// LazyVGrid(columns: gridColumns, alignment: .leading) { +// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in +// ForEach(rowMetadatas, id: \.self) { metadata in +// MediaCellView(metadata: metadata) +//// .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// .frame(height: 300) // } -// .cornerRadius(8.0) +// } +// } + + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in + HStack(spacing: 0) { + ForEach(rowMetadatas, id: \.self) { metadata in + MediaCellView(shrinkRatio: viewModel.getSize(rowMetadatas: rowMetadatas), metadata: metadata) + // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// .frame(height: 300) + } + } + } } } } } +} + // var body: some View { // Grid(0.. { - PreviewSnapshots( - configurations: [ - .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") - ], - configure: { _ in - NCMediaNew() - }) + let wtf = CGFloat(metadata.width) * shrinkRatio + let _ = print(wtf) } } @MainActor class MediaCellViewModel: ObservableObject { - @Published var thumbnail: UIImage = UIImage() + @Published private(set) var thumbnail: UIImage = UIImage() + private var metadata: tableMetadata = tableMetadata() - func downloadThumbnail(metadata: tableMetadata) { + func configure(metadata: tableMetadata) { + self.metadata = metadata + } + + func downloadThumbnail() { let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { @@ -98,10 +149,49 @@ struct NCMediaNew_Previews: PreviewProvider { } } else { thumbnail = UIImage(systemName: "plus")! -// // Perform thumbnail download -// NCOperationQueue.shared.downloadThumbnail(metadata: metadata) { downloadedImage in -// self.thumbnail = downloadedImage -// } + + let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! + let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! + let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + + var etagResource: String? + if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { + etagResource = metadata.etagResource + } + let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.downloadPreview( + fileNamePathOrFileId: fileNamePath, + fileNamePreviewLocalPath: fileNamePreviewLocalPath, + widthPreview: Int(UIScreen.main.bounds.width), + heightPreview: Int(UIScreen.main.bounds.height) / 2, + fileNameIconLocalPath: fileNameIconLocalPath, + sizeIcon: NCGlobal.shared.sizeIcon, + etag: etagResource, + options: options) { _, _, imageIcon, _, etag, error in + + if error == .success, let imageIcon = imageIcon { + NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag) + DispatchQueue.main.async { +// if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { +// UIView.transition(with: filePreviewImageView, +// duration: 0.75, +// options: .transitionCrossDissolve, +// animations: { filePreviewImageView.image = imageIcon }, +// completion: nil) + self.thumbnail = imageIcon +// } else { +// if self.view is UICollectionView { +// (self.view as? UICollectionView)?.reloadData() +// } else if self.view is UITableView { +// (self.view as? UITableView)?.reloadData() +// } +// } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) + } + } +// self.finish() + } } } } @@ -171,13 +261,126 @@ struct NCMediaNew_Previews: PreviewProvider { switch CCUtility.getMediaSortDate() { case "date": - self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)} ) + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) case "creationDate": - self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)} ) + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) case "uploadDate": - self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)} ) + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) default: break } } + +// func getSmallestHeight(rowMetadatas: [tableMetadata], metadata: tableMetadata) -> CGFloat { +//// let scaleFactor = (rowMetadatas.compactMap { $0.height }.max() ?? 0) / metadata.height +// +// var newSummedWidth: CGFloat = 0 +// +// for metadata in rowMetadatas { +// let height1 = CGFloat(metadata.height == 0 ? 336 : metadata.height) +// let width1 = CGFloat(metadata.width == 0 ? 336 : metadata.width) +// +// let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 +// let newHeight1 = height1 * scaleFactor1 +// let newWidth1 = width1 * scaleFactor1 +// +// newSummedWidth += CGFloat(newWidth1) +// } +// +// return CGFloat(metadata.height * scaleFactor) +// } + + func getSize(rowMetadatas: [tableMetadata]) -> CGFloat { + let screenWidth = UIScreen.main.bounds.width + var newSummedWidth: CGFloat = 0 + + for metadata in rowMetadatas { + let height1 = CGFloat(metadata.height == 0 ? 500 : metadata.height) + let width1 = CGFloat(metadata.width == 0 ? 500 : metadata.width) + + let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 + let newHeight1 = height1 * scaleFactor1 + let newWidth1 = width1 * scaleFactor1 + + newSummedWidth += CGFloat(newWidth1) + } + + let shrinkRatio: CGFloat = screenWidth / newSummedWidth + + return shrinkRatio + } +} + +struct NCMediaNew_Previews: PreviewProvider { + static var previews: some View { + snapshots.previews.previewLayout(.sizeThatFits) + } + + static var snapshots: PreviewSnapshots { + PreviewSnapshots( + configurations: [ + .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") + ], + configure: { _ in + NCMediaNew() + }) + } +} + +extension Array { + func chunked(into size: Int) -> [[Element]] { + return stride(from: 0, to: count, by: size).map { + Array(self[$0..: View where T: Hashable { + + // MARK: - Properties + var content: (T) -> Content + var list: [T] + var columns: Int + var showIndicators: Bool + var spacing: CGFloat + + init(list: [T], columns: Int, showIndicators: Bool = false, spacing: CGFloat = 10, @ViewBuilder content: @escaping (T) -> Content) { + self.content = content + self.list = list + self.columns = columns + self.showIndicators = showIndicators + self.spacing = spacing + } + + func setUpList() -> [[T]] { + var gridArray: [[T]] = Array(repeating: [], count: columns) + var currentIndex: Int = 0 + for object in list { + gridArray[currentIndex].append(object) + if currentIndex == (columns - 1) { + currentIndex = 0 + } else { + currentIndex += 1 + } + } + return gridArray + } + + // MARK: - Body + var body: some View { + ScrollView(.vertical, showsIndicators: showIndicators) { + HStack(alignment: .top, spacing: 0) { + ForEach(setUpList(), id: \.self) { columnData in + LazyVStack(spacing: 0) { + ForEach(columnData, id: \.self) { object in + let _ = print(columnData) + content(object) + } + } + + } + } + .padding(.vertical) + } + } } From 8890bccef45b83c10bbfe346bd18e3980cf27411 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 4 Sep 2023 13:56:58 +0200 Subject: [PATCH 005/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 313 ++++++++++++++----------------- 1 file changed, 136 insertions(+), 177 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index c1d57ea8cf..869f3744eb 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -41,53 +41,18 @@ struct NCMediaNew: View { @State private var minHeight: CGFloat = 0 var body: some View { - VStack { -// StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in -// MediaCellView(metadata: metadata) -// }) -// FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in -// MediaCellView(metadata: metadata) -// } + // StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in + // MediaCellView(metadata: metadata) + // }) + // FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in + // MediaCellView(metadata: metadata) + // } + GeometryReader { proxy in ScrollView { -// HStack(alignment: .top) { -// LazyVStack(spacing: 8) { -// ForEach(viewModel.metadatas.chunked(into: viewModel.metadatas.count / 2), id: \.self) { rowMetadatas in -// ForEach(rowMetadatas, id: \.self) { metadata in -// MediaCellView(metadata: metadata) -// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// } -// } -// } -// -// LazyVStack(spacing: 8) { -// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in -// ForEach(rowMetadatas, id: \.self) { metadata in -// MediaCellView(metadata: metadata) -// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// } -// } -// } -// } -// LazyVGrid(columns: gridColumns, alignment: .leading) { -// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in -// ForEach(rowMetadatas, id: \.self) { metadata in -// MediaCellView(metadata: metadata) -//// .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// .frame(height: 300) -// } -// } -// } - LazyVStack(alignment: .leading, spacing: 0) { ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in - HStack(spacing: 0) { - ForEach(rowMetadatas, id: \.self) { metadata in - MediaCellView(shrinkRatio: viewModel.getSize(rowMetadatas: rowMetadatas), metadata: metadata) - // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// .frame(height: 300) - } - } + MediaRow(metadatas: rowMetadatas) } } } @@ -110,89 +75,153 @@ struct NCMediaNew: View { // } // } +struct MediaRow: View { + let metadatas: [tableMetadata] + @StateObject private var viewModel = MediaCellViewModel() + + var body: some View { + HStack(spacing: 0) { + if viewModel.thumbnails.isEmpty { + ProgressView() + } else { + ForEach(viewModel.thumbnails, id: \.self) { thumbnail in + let _ = print(viewModel.thumbnails) + MediaCellView(shrinkRatio: viewModel.shrinkRatio, thumbnail: thumbnail) + // get image here using async await and pass it to the MediaCellView + // MediaCellView(shrinkRatio: viewModel.getSize(), metadata: metadata) + } + } + } + .onAppear { + viewModel.configure(metadatas: metadatas) + viewModel.downloadThumbnails() + } + } +} + struct MediaCellView: View { let shrinkRatio: CGFloat - let metadata: tableMetadata - @StateObject private var viewModel = MediaCellViewModel() + let thumbnail: UIImage +// let metadata: tableMetadata +// @StateObject private var viewModel = MediaCellViewModel() var body: some View { - Image(uiImage: viewModel.thumbnail) + Image(uiImage: thumbnail) .resizable() -// .scaledToFit() - .frame(width: CGFloat(metadata.width) * shrinkRatio, height: CGFloat(metadata.height) * shrinkRatio) -// .scaledToFit() - .onAppear { - viewModel.configure(metadata: metadata) - viewModel.downloadThumbnail() - } +// .scaledToFill() + .frame(width: CGFloat(thumbnail.size.width * shrinkRatio), height: CGFloat(thumbnail.size.height * shrinkRatio)) - let wtf = CGFloat(metadata.width) * shrinkRatio - let _ = print(wtf) +// let wtf = CGFloat(metadata.width) * shrinkRatio +// let _ = print(viewModel.thumbnail.size.width) +// let _ = print(viewModel.thumbnail.size.height) } } @MainActor class MediaCellViewModel: ObservableObject { - @Published private(set) var thumbnail: UIImage = UIImage() - private var metadata: tableMetadata = tableMetadata() + @Published private(set) var thumbnails: [UIImage] = [] + private var metadatas: [tableMetadata] = [] + var shrinkRatio: CGFloat = 0 - func configure(metadata: tableMetadata) { - self.metadata = metadata + func configure(metadatas: [tableMetadata]) { + self.metadatas = metadatas } - func downloadThumbnail() { - let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) + func downloadThumbnails() { + var thumbnails: [UIImage] = [] - if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { - // Load thumbnail from file - if let image = UIImage(contentsOfFile: thumbnailPath) { - thumbnail = image - } - } else { - thumbnail = UIImage(systemName: "plus")! + metadatas.enumerated().forEach { index, metadata in + let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) - let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! - let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! - let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { + // Load thumbnail from file + if let image = UIImage(contentsOfFile: thumbnailPath) { + thumbnails.append(image) - var etagResource: String? - if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { - etagResource = metadata.etagResource - } - let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) - - NextcloudKit.shared.downloadPreview( - fileNamePathOrFileId: fileNamePath, - fileNamePreviewLocalPath: fileNamePreviewLocalPath, - widthPreview: Int(UIScreen.main.bounds.width), - heightPreview: Int(UIScreen.main.bounds.height) / 2, - fileNameIconLocalPath: fileNameIconLocalPath, - sizeIcon: NCGlobal.shared.sizeIcon, - etag: etagResource, - options: options) { _, _, imageIcon, _, etag, error in - - if error == .success, let imageIcon = imageIcon { - NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag) - DispatchQueue.main.async { -// if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { -// UIView.transition(with: filePreviewImageView, -// duration: 0.75, -// options: .transitionCrossDissolve, -// animations: { filePreviewImageView.image = imageIcon }, -// completion: nil) - self.thumbnail = imageIcon -// } else { -// if self.view is UICollectionView { -// (self.view as? UICollectionView)?.reloadData() -// } else if self.view is UITableView { -// (self.view as? UITableView)?.reloadData() -// } -// } - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) - } + if thumbnails.count == self.metadatas.count { + self.thumbnails = thumbnails + shrinkRatio = getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) } -// self.finish() } + } else { + let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! + let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! + let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + + var etagResource: String? + if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { + etagResource = metadata.etagResource + } + let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.downloadPreview( + fileNamePathOrFileId: fileNamePath, + fileNamePreviewLocalPath: fileNamePreviewLocalPath, + widthPreview: Int(UIScreen.main.bounds.width) / 2, + heightPreview: Int(UIScreen.main.bounds.height) / 2, + fileNameIconLocalPath: fileNameIconLocalPath, + sizeIcon: NCGlobal.shared.sizeIcon, + etag: etagResource, + options: options) { _, _, imageIcon, _, etag, error in + + if error == .success, let image = imageIcon { + NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) + DispatchQueue.main.async { + // if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { + // UIView.transition(with: filePreviewImageView, + // duration: 0.75, + // options: .transitionCrossDissolve, + // animations: { filePreviewImageView.image = imageIcon }, + // completion: nil) + thumbnails.append(image) + // } else { + // if self.view is UICollectionView { + // (self.view as? UICollectionView)?.reloadData() + // } else if self.view is UITableView { + // (self.view as? UITableView)?.reloadData() + // } + // } + +// self.metadata.height = Int(self.thumbnail.size.height) +// self.metadata.width = Int(self.thumbnail.size.width) +// print(self.thumbnail.size.width) +// print(self.thumbnail.size.height) +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) + + print("------------") + print(thumbnails.count) + + if thumbnails.count == self.metadatas.count { + self.thumbnails = thumbnails + self.shrinkRatio = self.getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) + } + } + + + } + // self.finish() + } + } } + + } + + func getSize(rowMetadatas: [UIImage], fullWidth: CGFloat) -> CGFloat { + var newSummedWidth: CGFloat = 0 + let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 + for metadata in rowMetadatas { + let height1 = metadata.size.height + let width1 = metadata.size.width + + let scaleFactor1 = maxHeight / height1 +// let newHeight1 = height1 * scaleFactor1 + let newWidth1 = width1 * scaleFactor1 + + newSummedWidth += CGFloat(newWidth1) + } + + let shrinkRatio: CGFloat = fullWidth / newSummedWidth + + return shrinkRatio } } @@ -289,26 +318,6 @@ struct MediaCellView: View { // // return CGFloat(metadata.height * scaleFactor) // } - - func getSize(rowMetadatas: [tableMetadata]) -> CGFloat { - let screenWidth = UIScreen.main.bounds.width - var newSummedWidth: CGFloat = 0 - - for metadata in rowMetadatas { - let height1 = CGFloat(metadata.height == 0 ? 500 : metadata.height) - let width1 = CGFloat(metadata.width == 0 ? 500 : metadata.width) - - let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 - let newHeight1 = height1 * scaleFactor1 - let newWidth1 = width1 * scaleFactor1 - - newSummedWidth += CGFloat(newWidth1) - } - - let shrinkRatio: CGFloat = screenWidth / newSummedWidth - - return shrinkRatio - } } struct NCMediaNew_Previews: PreviewProvider { @@ -334,53 +343,3 @@ extension Array { } } } - -struct StaggeredGrid: View where T: Hashable { - - // MARK: - Properties - var content: (T) -> Content - var list: [T] - var columns: Int - var showIndicators: Bool - var spacing: CGFloat - - init(list: [T], columns: Int, showIndicators: Bool = false, spacing: CGFloat = 10, @ViewBuilder content: @escaping (T) -> Content) { - self.content = content - self.list = list - self.columns = columns - self.showIndicators = showIndicators - self.spacing = spacing - } - - func setUpList() -> [[T]] { - var gridArray: [[T]] = Array(repeating: [], count: columns) - var currentIndex: Int = 0 - for object in list { - gridArray[currentIndex].append(object) - if currentIndex == (columns - 1) { - currentIndex = 0 - } else { - currentIndex += 1 - } - } - return gridArray - } - - // MARK: - Body - var body: some View { - ScrollView(.vertical, showsIndicators: showIndicators) { - HStack(alignment: .top, spacing: 0) { - ForEach(setUpList(), id: \.self) { columnData in - LazyVStack(spacing: 0) { - ForEach(columnData, id: \.self) { object in - let _ = print(columnData) - content(object) - } - } - - } - } - .padding(.vertical) - } - } -} From 61aa890b73c7985671ba1ae86a0168901682b9fd Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 10:39:04 +0200 Subject: [PATCH 006/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 153 +++++++++++++++++++------------ 1 file changed, 93 insertions(+), 60 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 869f3744eb..fd8edcb71a 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -36,20 +36,14 @@ struct MinHeightPreferenceKey: PreferenceKey { struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() - @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) +// @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 5) @State private var minHeight: CGFloat = 0 var body: some View { - // StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in - // MediaCellView(metadata: metadata) - // }) - // FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in - // MediaCellView(metadata: metadata) - // } GeometryReader { proxy in ScrollView { - LazyVStack(alignment: .leading, spacing: 0) { + LazyVStack(alignment: .leading) { ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in MediaRow(metadatas: rowMetadatas) @@ -75,20 +69,32 @@ struct NCMediaNew: View { // } // } +struct RowData { + var scaledThumbnails: [ScaledThumbnail] = [] + var shrinkRatio: CGFloat = 0 +} + +struct ScaledThumbnail: Hashable { + let image: UIImage + var scaledSize: CGSize = .zero + let metadata: tableMetadata + + func hash(into hasher: inout Hasher) { + hasher.combine(image) + } +} + struct MediaRow: View { let metadatas: [tableMetadata] @StateObject private var viewModel = MediaCellViewModel() var body: some View { - HStack(spacing: 0) { - if viewModel.thumbnails.isEmpty { + HStack() { + if viewModel.rowData.scaledThumbnails.isEmpty { ProgressView() } else { - ForEach(viewModel.thumbnails, id: \.self) { thumbnail in - let _ = print(viewModel.thumbnails) - MediaCellView(shrinkRatio: viewModel.shrinkRatio, thumbnail: thumbnail) - // get image here using async await and pass it to the MediaCellView - // MediaCellView(shrinkRatio: viewModel.getSize(), metadata: metadata) + ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in + MediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) } } } @@ -100,16 +106,17 @@ struct MediaRow: View { } struct MediaCellView: View { + let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat - let thumbnail: UIImage +// let thumbnail: UIImage // let metadata: tableMetadata // @StateObject private var viewModel = MediaCellViewModel() var body: some View { - Image(uiImage: thumbnail) + Image(uiImage: thumbnail.image) .resizable() // .scaledToFill() - .frame(width: CGFloat(thumbnail.size.width * shrinkRatio), height: CGFloat(thumbnail.size.height * shrinkRatio)) + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) // let wtf = CGFloat(metadata.width) * shrinkRatio // let _ = print(viewModel.thumbnail.size.width) @@ -118,16 +125,18 @@ struct MediaCellView: View { } @MainActor class MediaCellViewModel: ObservableObject { - @Published private(set) var thumbnails: [UIImage] = [] +// @Published private(set) var thumbnails: [UIImage] = [] + @Published private(set) var rowData = RowData() + private var metadatas: [tableMetadata] = [] - var shrinkRatio: CGFloat = 0 +// var shrinkRatio: CGFloat = 0 func configure(metadatas: [tableMetadata]) { self.metadatas = metadatas } func downloadThumbnails() { - var thumbnails: [UIImage] = [] + var thumbnails: [ScaledThumbnail] = [] metadatas.enumerated().forEach { index, metadata in let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) @@ -135,11 +144,17 @@ struct MediaCellView: View { if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { // Load thumbnail from file if let image = UIImage(contentsOfFile: thumbnailPath) { - thumbnails.append(image) + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) if thumbnails.count == self.metadatas.count { - self.thumbnails = thumbnails - shrinkRatio = getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + + let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + + rowData.scaledThumbnails = thumbnails + rowData.shrinkRatio = shrinkRatio } } } else { @@ -168,61 +183,79 @@ struct MediaCellView: View { DispatchQueue.main.async { // if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { // UIView.transition(with: filePreviewImageView, - // duration: 0.75, - // options: .transitionCrossDissolve, - // animations: { filePreviewImageView.image = imageIcon }, - // completion: nil) - thumbnails.append(image) - // } else { - // if self.view is UICollectionView { - // (self.view as? UICollectionView)?.reloadData() - // } else if self.view is UITableView { - // (self.view as? UITableView)?.reloadData() - // } - // } - -// self.metadata.height = Int(self.thumbnail.size.height) -// self.metadata.width = Int(self.thumbnail.size.width) -// print(self.thumbnail.size.width) -// print(self.thumbnail.size.height) -// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) - - print("------------") - print(thumbnails.count) + // duration: 0.75, + // options: .transitionCrossDissolve, + // animations: { filePreviewImageView.image = imageIcon }, + // completion: nil) + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) if thumbnails.count == self.metadatas.count { - self.thumbnails = thumbnails - self.shrinkRatio = self.getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) - } - } + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: 1000) + self.rowData.scaledThumbnails = thumbnails + self.rowData.shrinkRatio = shrinkRatio + } + } } - // self.finish() } } } } - func getSize(rowMetadatas: [UIImage], fullWidth: CGFloat) -> CGFloat { - var newSummedWidth: CGFloat = 0 - let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 - for metadata in rowMetadatas { - let height1 = metadata.size.height - let width1 = metadata.size.width + func getScaledThumbnailSize(of thumbnail: ScaledThumbnail, thumbnailsInRow thumbnails: [ScaledThumbnail]) -> CGSize { + let maxHeight = thumbnails.compactMap { CGFloat($0.image.size.height) }.max() ?? 0 - let scaleFactor1 = maxHeight / height1 -// let newHeight1 = height1 * scaleFactor1 - let newWidth1 = width1 * scaleFactor1 + let height = thumbnail.image.size.height + let width = thumbnail.image.size.width + + let scaleFactor = maxHeight / height + let newHeight = height * scaleFactor + let newWidth = width * scaleFactor - newSummedWidth += CGFloat(newWidth1) +// return .init(image: thumbnail, scaledSize: .init(width: newWidth, height: newHeight)) + + + return .init(width: newWidth, height: newHeight) + } + + func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { + var newSummedWidth: CGFloat = 0 +// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 + for thumbnail in thumbnails { + newSummedWidth += CGFloat(thumbnail.scaledSize.width) } let shrinkRatio: CGFloat = fullWidth / newSummedWidth return shrinkRatio } + +// func getShrinkRatio(rowMetadatas: [UIImage], fullWidth: CGFloat) -> (height: CGFloat, shrinkRatio: CGFloat) { +// var newSummedWidth: CGFloat = 0 +// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 +// for metadata in rowMetadatas { +// let height1 = metadata.size.height +// let width1 = metadata.size.width +// +// let scaleFactor1 = maxHeight / height1 +// let newHeight1 = height1 * scaleFactor1 +// let newWidth1 = width1 * scaleFactor1 +// +//// scaledThumbnails.append(.init(image: metadata, scaledSize: .init(width: newWidth1, height: newHeight1))) +// +// newSummedWidth += CGFloat(newWidth1) +// } +// +// let shrinkRatio: CGFloat = fullWidth / newSummedWidth +// +// return shrinkRatio +// } + } @MainActor class NCMediaViewModel: ObservableObject { From a92d9fc69c5285e0d09f2e2e831b361f063533d0 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 10:46:13 +0200 Subject: [PATCH 007/103] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 ++ iOSClient/Media/NCMediaNew.swift | 41 +----------------- iOSClient/Media/NCMediaRow.swift | 67 +++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 40 deletions(-) create mode 100644 iOSClient/Media/NCMediaRow.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 6622e753b2..43abd6c12f 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -127,6 +127,7 @@ F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */; }; F3953BD72A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */; }; F3A7AFC62A41AA82001FC89C /* BaseUIXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */; }; + F3AEDCA72AA720F800FDFA44 /* NCMediaRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */; }; F3BB464D2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3BB464C2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib */; }; F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; @@ -845,6 +846,7 @@ F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNCMoreCell.swift; sourceTree = ""; }; F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseIntegrationXCTestCase.swift; sourceTree = ""; }; F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUIXCTestCase.swift; sourceTree = ""; }; + F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaRow.swift; sourceTree = ""; }; F3BB464C2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCMoreAppSuggestionsCell.xib; sourceTree = ""; }; F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreUserCell.swift; sourceTree = ""; }; F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; @@ -2376,6 +2378,7 @@ F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, + F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */, ); path = Media; sourceTree = ""; @@ -3723,6 +3726,7 @@ F7B6B70427C4E7FA00A7F6EB /* NCScan+CollectionView.swift in Sources */, F7C30DF6291BC0CA0017149B /* NCNetworkingE2EEUpload.swift in Sources */, F7501C332212E57500FB1415 /* NCMedia.swift in Sources */, + F3AEDCA72AA720F800FDFA44 /* NCMediaRow.swift in Sources */, F72944F22A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */, F70BFC7420E0FA7D00C67599 /* NCUtility.swift in Sources */, F79EDAA526B004980007D134 /* NCPlayer.swift in Sources */, diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index fd8edcb71a..0e1676365f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -17,14 +17,6 @@ class NCMediaUIHostingController: UIHostingController { } } -// extension Array { -// func chunked(into size: Int) -> [[Element]] { -// return stride(from: 0, to: count, by: size).map { -// Array(self[$0 ..< Swift.min($0 + size, count)]) -// } -// } -// } - struct MinHeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 @@ -181,12 +173,6 @@ struct MediaCellView: View { if error == .success, let image = imageIcon { NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) DispatchQueue.main.async { - // if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { - // UIView.transition(with: filePreviewImageView, - // duration: 0.75, - // options: .transitionCrossDissolve, - // animations: { filePreviewImageView.image = imageIcon }, - // completion: nil) thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) if thumbnails.count == self.metadatas.count { @@ -217,15 +203,12 @@ struct MediaCellView: View { let newHeight = height * scaleFactor let newWidth = width * scaleFactor -// return .init(image: thumbnail, scaledSize: .init(width: newWidth, height: newHeight)) - - return .init(width: newWidth, height: newHeight) } func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { var newSummedWidth: CGFloat = 0 -// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 + for thumbnail in thumbnails { newSummedWidth += CGFloat(thumbnail.scaledSize.width) } @@ -234,28 +217,6 @@ struct MediaCellView: View { return shrinkRatio } - -// func getShrinkRatio(rowMetadatas: [UIImage], fullWidth: CGFloat) -> (height: CGFloat, shrinkRatio: CGFloat) { -// var newSummedWidth: CGFloat = 0 -// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 -// for metadata in rowMetadatas { -// let height1 = metadata.size.height -// let width1 = metadata.size.width -// -// let scaleFactor1 = maxHeight / height1 -// let newHeight1 = height1 * scaleFactor1 -// let newWidth1 = width1 * scaleFactor1 -// -//// scaledThumbnails.append(.init(image: metadata, scaledSize: .init(width: newWidth1, height: newHeight1))) -// -// newSummedWidth += CGFloat(newWidth1) -// } -// -// let shrinkRatio: CGFloat = fullWidth / newSummedWidth -// -// return shrinkRatio -// } - } @MainActor class NCMediaViewModel: ObservableObject { diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift new file mode 100644 index 0000000000..8901ccd6ff --- /dev/null +++ b/iOSClient/Media/NCMediaRow.swift @@ -0,0 +1,67 @@ +// +// NCMediaRow.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI +import PreviewSnapshots + +//struct NCMediaRow: View { +// let metadatas: [tableMetadata] +// @StateObject private var viewModel = MediaCellViewModel() +// +// var body: some View { +// HStack() { +// if viewModel.rowData.scaledThumbnails.isEmpty { +// ProgressView() +// } else { +// ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in +// MediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) +// } +// } +// } +// .onAppear { +// viewModel.configure(metadatas: metadatas) +// viewModel.downloadThumbnails() +// } +// } +//} +// +//struct MediaCellView: View { +// let thumbnail: ScaledThumbnail +// let shrinkRatio: CGFloat +//// let thumbnail: UIImage +//// let metadata: tableMetadata +//// @StateObject private var viewModel = MediaCellViewModel() +// +// var body: some View { +// Image(uiImage: thumbnail.image) +// .resizable() +//// .scaledToFill() +// .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) +// +//// let wtf = CGFloat(metadata.width) * shrinkRatio +//// let _ = print(viewModel.thumbnail.size.width) +//// let _ = print(viewModel.thumbnail.size.height) +// } +//} +// +//struct NCMediaRow_Previews: PreviewProvider { +// static var previews: some View { +// snapshots.previews.previewLayout(.sizeThatFits) +// } +// +// static var snapshots: PreviewSnapshots { +// PreviewSnapshots( +// configurations: [ +// .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") +// ], +// configure: { _ in +// NCMediaRow() +// }) +// } +//} + From dcd3f835fed4fded087090fd4c3f6e31c6e1aa26 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 12:45:47 +0200 Subject: [PATCH 008/103] Separate components Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 12 + iOSClient/Extensions/Array+Extension.swift | 7 +- iOSClient/Media/Cell/NCMediaCellView.swift | 20 ++ iOSClient/Media/NCMediaNew.swift | 276 +-------------------- iOSClient/Media/NCMediaRow.swift | 75 ++---- iOSClient/Media/NCMediaRowViewModel.swift | 110 ++++++++ iOSClient/Media/NCMediaViewModel.swift | 75 ++++++ 7 files changed, 244 insertions(+), 331 deletions(-) create mode 100644 iOSClient/Media/Cell/NCMediaCellView.swift create mode 100644 iOSClient/Media/NCMediaRowViewModel.swift create mode 100644 iOSClient/Media/NCMediaViewModel.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 43abd6c12f..0753c227c9 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -132,6 +132,9 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */; }; + F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */; }; + F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -851,6 +854,9 @@ F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreUserCell.swift; sourceTree = ""; }; F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = ""; }; + F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaRowViewModel.swift; sourceTree = ""; }; + F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCellView.swift; sourceTree = ""; }; + F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaViewModel.swift; sourceTree = ""; }; F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaNew.swift; sourceTree = ""; }; F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = ""; }; F700510022DF63AC003A3356 /* NCShare.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShare.storyboard; sourceTree = ""; }; @@ -1747,6 +1753,7 @@ children = ( F77444F322281649000D5EB0 /* NCGridMediaCell.swift */, F77444F422281649000D5EB0 /* NCGridMediaCell.xib */, + F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */, ); path = Cell; sourceTree = ""; @@ -2378,7 +2385,9 @@ F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, + F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */, F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */, + F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */, ); path = Media; sourceTree = ""; @@ -3705,6 +3714,7 @@ AF817EF1274BC781009ED85B /* NCUserBaseUrl.swift in Sources */, F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */, AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */, + F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */, F7E41316294A19B300839300 /* UIView+Extension.swift in Sources */, F31F69502A2F707E00162F76 /* SwiftUIView+Extensions.swift in Sources */, F7C30E00291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */, @@ -3760,6 +3770,7 @@ F7B7504B2397D38F004E13EC /* UIImage+Extension.swift in Sources */, F7EFC0CD256BF8DD00461AAD /* NCUserStatus.swift in Sources */, AF3FDCC22796ECC300710F60 /* NCTrash+CollectionView.swift in Sources */, + F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */, F70D7C3725FFBF82002B9E34 /* NCCollectionViewCommon.swift in Sources */, F76D364628A4F8BF00214537 /* NCActivityIndicator.swift in Sources */, F7020FCE2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift in Sources */, @@ -3819,6 +3830,7 @@ F717402E24F699A5000C87D5 /* NCFavorite.swift in Sources */, AF2D7C7E2742559100ADF566 /* NCShareUserCell.swift in Sources */, AF7E505027A2D92300B5E4AF /* NCSelectableNavigationView.swift in Sources */, + F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */, F74DE14325135B6800917068 /* NCTransfers.swift in Sources */, AF4BF614275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */, AF4BF61E27562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */, diff --git a/iOSClient/Extensions/Array+Extension.swift b/iOSClient/Extensions/Array+Extension.swift index 0ff4eeae15..190f2f3975 100644 --- a/iOSClient/Extensions/Array+Extension.swift +++ b/iOSClient/Extensions/Array+Extension.swift @@ -26,7 +26,6 @@ import Foundation // https://stackoverflow.com/questions/33861036/unique-objects-inside-a-array-swift/45023247#45023247 extension Array { - func unique(map: ((Element) -> (T))) -> [Element] { var set = Set() // the unique list kept in a Set for fast retrieval var arrayOrdered = [Element]() // keeping the unique list of elements but ordered @@ -37,4 +36,10 @@ extension Array { return arrayOrdered } + + func chunked(into size: Int) -> [[Element]] { + return stride(from: 0, to: count, by: size).map { + Array(self[$0.. { } } -struct MinHeightPreferenceKey: PreferenceKey { - static var defaultValue: CGFloat = 0 - - static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { - value = max(value, nextValue()) - } -} - struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() -// @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 5) - - @State private var minHeight: CGFloat = 0 - var body: some View { GeometryReader { proxy in ScrollView { LazyVStack(alignment: .leading) { ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in - - MediaRow(metadatas: rowMetadatas) + NCMediaRow(metadatas: rowMetadatas) } } } @@ -46,21 +33,6 @@ struct NCMediaNew: View { } } -// var body: some View { -// Grid(0.. CGSize { - let maxHeight = thumbnails.compactMap { CGFloat($0.image.size.height) }.max() ?? 0 - - let height = thumbnail.image.size.height - let width = thumbnail.image.size.width - - let scaleFactor = maxHeight / height - let newHeight = height * scaleFactor - let newWidth = width * scaleFactor - - return .init(width: newWidth, height: newHeight) - } - - func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { - var newSummedWidth: CGFloat = 0 - - for thumbnail in thumbnails { - newSummedWidth += CGFloat(thumbnail.scaledSize.width) - } - - let shrinkRatio: CGFloat = fullWidth / newSummedWidth - - return shrinkRatio - } -} - -@MainActor class NCMediaViewModel: ObservableObject { - @Published var metadatas: [tableMetadata] = [] - - private var account: String = "" - private var lastContentOffsetY: CGFloat = 0 - private var mediaPath = "" - private var livePhoto: Bool = false - private var predicateDefault: NSPredicate? - private var predicate: NSPredicate? - private let appDelegate = UIApplication.shared.delegate as? AppDelegate - internal var filterClassTypeImage = false - internal var filterClassTypeVideo = false - - init() { - reloadDataSourceWithCompletion { _ in } - } - - @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { - guard let appDelegate, !appDelegate.account.isEmpty else { return } - - if account != appDelegate.account { - self.metadatas = [] - account = appDelegate.account -// DispatchQueue.main.async { self.collectionView?.reloadData() } - } - -// DispatchQueue.global().async { - self.queryDB(isForced: true) -// DispatchQueue.main.sync { -// self.reloadDataThenPerform { -// self.updateMediaControlVisibility() -// self.mediaCommandTitle() -// completion(self.metadatas) -// } -// } -// } - } - - func queryDB(isForced: Bool = false) { - guard let appDelegate else { return } - - livePhoto = CCUtility.getLivePhoto() - - if let activeAccount = NCManageDatabase.shared.getActiveAccount() { - self.mediaPath = activeAccount.mediaPath - } - - let startServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath - - predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue) - - if filterClassTypeImage { - predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) - } else if filterClassTypeVideo { - predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) - } else { - predicate = predicateDefault - } - - guard let predicate = predicate else { return } - - metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) - - switch CCUtility.getMediaSortDate() { - case "date": - self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) - case "creationDate": - self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) - case "uploadDate": - self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) - default: - break - } - } - -// func getSmallestHeight(rowMetadatas: [tableMetadata], metadata: tableMetadata) -> CGFloat { -//// let scaleFactor = (rowMetadatas.compactMap { $0.height }.max() ?? 0) / metadata.height -// -// var newSummedWidth: CGFloat = 0 -// -// for metadata in rowMetadatas { -// let height1 = CGFloat(metadata.height == 0 ? 336 : metadata.height) -// let width1 = CGFloat(metadata.width == 0 ? 336 : metadata.width) -// -// let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 -// let newHeight1 = height1 * scaleFactor1 -// let newWidth1 = width1 * scaleFactor1 -// -// newSummedWidth += CGFloat(newWidth1) -// } -// -// return CGFloat(metadata.height * scaleFactor) -// } -} - struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) @@ -329,11 +63,3 @@ struct NCMediaNew_Previews: PreviewProvider { }) } } - -extension Array { - func chunked(into size: Int) -> [[Element]] { - return stride(from: 0, to: count, by: size).map { - Array(self[$0.. { -// PreviewSnapshots( -// configurations: [ -// .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") -// ], -// configure: { _ in -// NCMediaRow() -// }) -// } -//} +struct NCMediaRow: View { + let metadatas: [tableMetadata] + @StateObject private var viewModel = NCMediaRowViewModel() + + var body: some View { + HStack() { + if viewModel.rowData.scaledThumbnails.isEmpty { + ProgressView() + } else { + ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in + NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) + } + } + } + .onAppear { + viewModel.configure(metadatas: metadatas) + viewModel.downloadThumbnails() + } + } +} diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift new file mode 100644 index 0000000000..debefaa909 --- /dev/null +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -0,0 +1,110 @@ +// +// NCMediaRowViewModel.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import NextcloudKit + +@MainActor class NCMediaRowViewModel: ObservableObject { + @Published private(set) var rowData = RowData() + + private var metadatas: [tableMetadata] = [] + + func configure(metadatas: [tableMetadata]) { + self.metadatas = metadatas + } + + func downloadThumbnails() { + var thumbnails: [ScaledThumbnail] = [] + + metadatas.enumerated().forEach { index, metadata in + let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) + + if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { + // Load thumbnail from file + if let image = UIImage(contentsOfFile: thumbnailPath) { + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + + if thumbnails.count == self.metadatas.count { + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + + let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + + rowData.scaledThumbnails = thumbnails + rowData.shrinkRatio = shrinkRatio + } + } + } else { + let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! + let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! + let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + + var etagResource: String? + if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { + etagResource = metadata.etagResource + } + let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.downloadPreview( + fileNamePathOrFileId: fileNamePath, + fileNamePreviewLocalPath: fileNamePreviewLocalPath, + widthPreview: Int(UIScreen.main.bounds.width) / 2, + heightPreview: Int(UIScreen.main.bounds.height) / 2, + fileNameIconLocalPath: fileNameIconLocalPath, + sizeIcon: NCGlobal.shared.sizeIcon, + etag: etagResource, + options: options) { _, _, imageIcon, _, etag, error in + if error == .success, let image = imageIcon { + NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) + DispatchQueue.main.async { + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + + if thumbnails.count == self.metadatas.count { + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + + self.rowData.scaledThumbnails = thumbnails + self.rowData.shrinkRatio = shrinkRatio + } + } + } + } + } + } + + } + + func getScaledThumbnailSize(of thumbnail: ScaledThumbnail, thumbnailsInRow thumbnails: [ScaledThumbnail]) -> CGSize { + let maxHeight = thumbnails.compactMap { CGFloat($0.image.size.height) }.max() ?? 0 + + let height = thumbnail.image.size.height + let width = thumbnail.image.size.width + + let scaleFactor = maxHeight / height + let newHeight = height * scaleFactor + let newWidth = width * scaleFactor + + return .init(width: newWidth, height: newHeight) + } + + func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { + var newSummedWidth: CGFloat = 0 + + for thumbnail in thumbnails { + newSummedWidth += CGFloat(thumbnail.scaledSize.width) + } + + let shrinkRatio: CGFloat = fullWidth / newSummedWidth + + return shrinkRatio + } +} diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift new file mode 100644 index 0000000000..edd01a03d0 --- /dev/null +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -0,0 +1,75 @@ +// +// NCMediaViewModel.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import NextcloudKit + +@MainActor class NCMediaViewModel: ObservableObject { + @Published var metadatas: [tableMetadata] = [] + + private var account: String = "" + private var lastContentOffsetY: CGFloat = 0 + private var mediaPath = "" + private var livePhoto: Bool = false + private var predicateDefault: NSPredicate? + private var predicate: NSPredicate? + private let appDelegate = UIApplication.shared.delegate as? AppDelegate + internal var filterClassTypeImage = false + internal var filterClassTypeVideo = false + + init() { + reloadDataSourceWithCompletion { _ in } + } + + @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { + guard let appDelegate, !appDelegate.account.isEmpty else { return } + + if account != appDelegate.account { + self.metadatas = [] + account = appDelegate.account + } + + self.queryDB(isForced: true) + } + + func queryDB(isForced: Bool = false) { + guard let appDelegate else { return } + + livePhoto = CCUtility.getLivePhoto() + + if let activeAccount = NCManageDatabase.shared.getActiveAccount() { + self.mediaPath = activeAccount.mediaPath + } + + let startServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath + + predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue) + + if filterClassTypeImage { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) + } else if filterClassTypeVideo { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) + } else { + predicate = predicateDefault + } + + guard let predicate = predicate else { return } + + metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) + + switch CCUtility.getMediaSortDate() { + case "date": + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) + case "creationDate": + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) + case "uploadDate": + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) + default: + break + } + } +} From 17f7ba74e2d9ecab302994c531e1911921b50f3a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 15:02:04 +0200 Subject: [PATCH 009/103] WIP Signed-off-by: Milen Pivchev --- .../Extensions/SwiftUIView+Extensions.swift | 17 +++++++++++ iOSClient/Media/NCMediaNew.swift | 28 +++++++------------ iOSClient/Media/NCMediaRow.swift | 7 +++-- iOSClient/Media/NCMediaRowViewModel.swift | 26 +++++++++++++---- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift index 3e7fe20d7d..303daa231c 100644 --- a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift +++ b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift @@ -9,10 +9,27 @@ import Foundation import SwiftUI +// Custom view modifier to track rotation and call an action +struct DeviceOrientationViewModifier: ViewModifier { + let action: (UIDeviceOrientation) -> Void + + func body(content: Content) -> some View { + content + .onAppear() + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + action(UIDevice.current.orientation) + } + } +} + extension SwiftUI.View { func toVC() -> UIViewController { let vc = UIHostingController (rootView: self) vc.view.frame = UIScreen.main.bounds return vc } + + func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View { + self.modifier(DeviceOrientationViewModifier(action: action)) + } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 2efec4843f..ce9986cdf9 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -19,35 +19,27 @@ class NCMediaUIHostingController: UIHostingController { struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() + @State var columns = 2 var body: some View { GeometryReader { proxy in ScrollView { - LazyVStack(alignment: .leading) { - ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas) + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: proxy) } } + }.onRotate { orientation in + if orientation.isLandscapeHardCheck { + columns = 6 + } else { + columns = 2 + } } } } } -struct RowData { - var scaledThumbnails: [ScaledThumbnail] = [] - var shrinkRatio: CGFloat = 0 -} - -struct ScaledThumbnail: Hashable { - let image: UIImage - var scaledSize: CGSize = .zero - let metadata: tableMetadata - - func hash(into hasher: inout Hasher) { - hasher.combine(image) - } -} - struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index ea730a987b..39aa738512 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -11,10 +11,13 @@ import PreviewSnapshots struct NCMediaRow: View { let metadatas: [tableMetadata] + let geometryProxy: GeometryProxy + @StateObject private var viewModel = NCMediaRowViewModel() + private let spacing: CGFloat = 2 var body: some View { - HStack() { + HStack(spacing: spacing) { if viewModel.rowData.scaledThumbnails.isEmpty { ProgressView() } else { @@ -25,7 +28,7 @@ struct NCMediaRow: View { } .onAppear { viewModel.configure(metadatas: metadatas) - viewModel.downloadThumbnails() + viewModel.downloadThumbnails(rowWidth: geometryProxy.size.width, spacing: spacing) } } } diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift index debefaa909..e507735c50 100644 --- a/iOSClient/Media/NCMediaRowViewModel.swift +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -9,6 +9,21 @@ import Foundation import NextcloudKit +struct RowData { + var scaledThumbnails: [ScaledThumbnail] = [] + var shrinkRatio: CGFloat = 0 +} + +struct ScaledThumbnail: Hashable { + let image: UIImage + var scaledSize: CGSize = .zero + let metadata: tableMetadata + + func hash(into hasher: inout Hasher) { + hasher.combine(image) + } +} + @MainActor class NCMediaRowViewModel: ObservableObject { @Published private(set) var rowData = RowData() @@ -18,7 +33,7 @@ import NextcloudKit self.metadatas = metadatas } - func downloadThumbnails() { + func downloadThumbnails(rowWidth: CGFloat, spacing: CGFloat) { var thumbnails: [ScaledThumbnail] = [] metadatas.enumerated().forEach { index, metadata in @@ -34,7 +49,7 @@ import NextcloudKit thumbnails[index].scaledSize = getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) } - let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) rowData.scaledThumbnails = thumbnails rowData.shrinkRatio = shrinkRatio @@ -70,7 +85,7 @@ import NextcloudKit thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) } - let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) self.rowData.scaledThumbnails = thumbnails self.rowData.shrinkRatio = shrinkRatio @@ -96,14 +111,15 @@ import NextcloudKit return .init(width: newWidth, height: newHeight) } - func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { + func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat, spacing: CGFloat) -> CGFloat { var newSummedWidth: CGFloat = 0 for thumbnail in thumbnails { newSummedWidth += CGFloat(thumbnail.scaledSize.width) } - let shrinkRatio: CGFloat = fullWidth / newSummedWidth + let spacingWidth = spacing * CGFloat(thumbnails.count - 1) + let shrinkRatio: CGFloat = (fullWidth - spacingWidth) / newSummedWidth return shrinkRatio } From 8d4a2ba3e70ea4f86cf17cb30426de709f2c1203 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 15:52:57 +0200 Subject: [PATCH 010/103] Add top header Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index ce9986cdf9..2c18a0bc32 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -22,14 +22,25 @@ struct NCMediaNew: View { @State var columns = 2 var body: some View { - GeometryReader { proxy in - ScrollView { - LazyVStack(alignment: .leading, spacing: 2) { - ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: proxy) + GeometryReader { geometry in + ZStack(alignment: .top) { + ScrollView { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: geometry) + } } } - }.onRotate { orientation in + + HStack(content: { + Text("Placeholder") + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(.white) + }) + .frame(maxWidth: .infinity) + .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + } + .onRotate { orientation in if orientation.isLandscapeHardCheck { columns = 6 } else { From 63182e7d16b6e32c71ecda0433ada2feb90f4e11 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 11 Sep 2023 14:37:43 +0200 Subject: [PATCH 011/103] Add notifications Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaViewModel.swift | 82 +++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index edd01a03d0..c74dcece2b 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -23,6 +23,20 @@ import NextcloudKit init() { reloadDataSourceWithCompletion { _ in } + + NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(moveFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) } @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { @@ -33,7 +47,7 @@ import NextcloudKit account = appDelegate.account } - self.queryDB(isForced: true) + self.queryDB(isForced: true) } func queryDB(isForced: Bool = false) { @@ -73,3 +87,69 @@ import NextcloudKit } } } + +// MARK: Notifications + +extension NCMediaViewModel { + @objc func deleteFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError else { return } + let onlyLocalCache: Bool = userInfo["onlyLocalCache"] as? Bool ?? false + + self.queryDB(isForced: true) + + if error == .success, let indexPath = userInfo["indexPath"] as? [IndexPath], !indexPath.isEmpty, !onlyLocalCache { + // collectionView?.performBatchUpdates({ + // collectionView?.deleteItems(at: indexPath) + // }, completion: { _ in + // self.collectionView?.reloadData() + // }) + } else { + if error != .success { + NCContentPresenter.shared.showError(error: error) + } + // self.collectionView?.reloadData() + } + + // if let hud = userInfo["hud"] as? JGProgressHUD { + // hud.dismiss() + // } + } + + @objc func moveFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary? else { return } + + // if let hud = userInfo["hud"] as? JGProgressHUD { + // hud.dismiss() + // } + } + + @objc func copyFile(_ notification: NSNotification) { + + moveFile(notification) + } + + @objc func renameFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + account == appDelegate?.account + else { return } + + self.reloadDataSourceWithCompletion { _ in } + } + + @objc func uploadedFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError, + error == .success, + let account = userInfo["account"] as? String, + account == appDelegate?.account + else { return } + + self.reloadDataSourceWithCompletion { _ in } + } +} From 5327311360249806b27bc081d83309674d798eca Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 11 Sep 2023 18:33:31 +0200 Subject: [PATCH 012/103] Try to get first cell in list Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCellView.swift | 30 +++++++++++++++++++--- iOSClient/Media/NCMediaNew.swift | 18 ++++++++++--- iOSClient/Media/NCMediaRow.swift | 5 ++-- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCellView.swift b/iOSClient/Media/Cell/NCMediaCellView.swift index 94a14f5a2c..98793a4bd8 100644 --- a/iOSClient/Media/Cell/NCMediaCellView.swift +++ b/iOSClient/Media/Cell/NCMediaCellView.swift @@ -11,10 +11,34 @@ import SwiftUI struct NCMediaCellView: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat + let outerProxy: GeometryProxy + @Binding var title: String var body: some View { - Image(uiImage: thumbnail.image) - .resizable() - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + GeometryReader { geometry in + Image(uiImage: thumbnail.image) + .resizable() + // .scaledToFit() + // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in + if isInView(innerRect: rect, isIn: outerProxy) { + print(thumbnail.metadata.fileName) + title = thumbnail.metadata.fileName + } + } + } + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + } + + private func isInView(innerRect:CGRect, isIn outerProxy:GeometryProxy) -> Bool { + let innerOrigin = innerRect.origin.x + let imageWidth = innerRect.width + let scrollOrigin = outerProxy.frame(in: .global).origin.x + let scrollWidth = outerProxy.size.width + if innerOrigin + imageWidth < scrollOrigin + scrollWidth && innerOrigin + imageWidth > scrollOrigin || + innerOrigin + imageWidth > scrollOrigin && innerOrigin < scrollOrigin + scrollWidth { + return true + } + return false } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 2c18a0bc32..271688b907 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -20,20 +20,28 @@ class NCMediaUIHostingController: UIHostingController { struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() @State var columns = 2 + @State var title = "" + + public static let scrollViewCoordinateSpace = "scrollView" var body: some View { - GeometryReader { geometry in + GeometryReader { outerProxy in ZStack(alignment: .top) { ScrollView { LazyVStack(alignment: .leading, spacing: 2) { ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: geometry) + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) +// .onChange(of: geometry.frame(in: .local)) { rect in +// if isInView(innerRect: rect, isIn: outerProxy) { +// print("WOW") +// } +// } } } - } + }.coordinateSpace(name: NCMediaNew.scrollViewCoordinateSpace) HStack(content: { - Text("Placeholder") + Text(title) .font(.system(size: 20, weight: .bold)) .foregroundStyle(.white) }) @@ -49,8 +57,10 @@ struct NCMediaNew: View { } } } + } + struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 39aa738512..232528e865 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -12,7 +12,8 @@ import PreviewSnapshots struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy - + @Binding var title: String + @StateObject private var viewModel = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -22,7 +23,7 @@ struct NCMediaRow: View { ProgressView() } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) + NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) } } } From d2c5707af03001e62c7e0411ce4eab7674b00f2e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 13:41:33 +0200 Subject: [PATCH 013/103] Add date update Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 25 ++++++++-- iOSClient/Media/Cell/NCMediaCell.swift | 57 ++++++++++++++++++++++ iOSClient/Media/Cell/NCMediaCellView.swift | 44 ----------------- iOSClient/Media/NCMediaNew.swift | 25 ++++++---- iOSClient/Media/NCMediaRow.swift | 3 +- 5 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 iOSClient/Media/Cell/NCMediaCell.swift delete mode 100644 iOSClient/Media/Cell/NCMediaCellView.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 0753c227c9..6b3c6164da 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -132,8 +132,9 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3C0FB702AB06AB9007E7E30 /* VisibilityTrackingScrollView in Frameworks */ = {isa = PBXBuildFile; productRef = F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */; }; F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */; }; - F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */; }; + F3E3B3BB2AA7396F006D08F5 /* NCMediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */; }; F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -855,7 +856,7 @@ F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = ""; }; F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaRowViewModel.swift; sourceTree = ""; }; - F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCellView.swift; sourceTree = ""; }; + F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCell.swift; sourceTree = ""; }; F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaViewModel.swift; sourceTree = ""; }; F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaNew.swift; sourceTree = ""; }; F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = ""; }; @@ -1490,6 +1491,7 @@ F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */, F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */, F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */, + F3C0FB702AB06AB9007E7E30 /* VisibilityTrackingScrollView in Frameworks */, F770768E263A8C3400A1BA94 /* FloatingPanel in Frameworks */, F710FC7C277B7D0000AA9FBF /* RealmSwift in Frameworks */, F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */, @@ -1753,7 +1755,7 @@ children = ( F77444F322281649000D5EB0 /* NCGridMediaCell.swift */, F77444F422281649000D5EB0 /* NCGridMediaCell.xib */, - F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */, + F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */, ); path = Cell; sourceTree = ""; @@ -2900,6 +2902,7 @@ F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, F34624522AA08C5700FAA7B1 /* FlowGrid */, + F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3076,6 +3079,7 @@ F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */, + F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -3714,7 +3718,7 @@ AF817EF1274BC781009ED85B /* NCUserBaseUrl.swift in Sources */, F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */, AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */, - F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */, + F3E3B3BB2AA7396F006D08F5 /* NCMediaCell.swift in Sources */, F7E41316294A19B300839300 /* UIView+Extension.swift in Sources */, F31F69502A2F707E00162F76 /* SwiftUIView+Extensions.swift in Sources */, F7C30E00291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */, @@ -4932,6 +4936,14 @@ version = 1.0.0; }; }; + F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/elegantchaos/VisibilityTrackingScrollView"; + requirement = { + kind = exactVersion; + version = 1.0.3; + }; + }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; @@ -5229,6 +5241,11 @@ package = F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */; productName = FlowGrid; }; + F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */ = { + isa = XCSwiftPackageProductDependency; + package = F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */; + productName = VisibilityTrackingScrollView; + }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift new file mode 100644 index 0000000000..9005b3b39e --- /dev/null +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -0,0 +1,57 @@ +// +// NCMediaCellView.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI +import VisibilityTrackingScrollView + +struct NCMediaCell: View { + let thumbnail: ScaledThumbnail + let shrinkRatio: CGFloat + let outerProxy: GeometryProxy + @Binding var title: String +// @State private var visibleIndex: Set = [0,1] + @State private var visibleMetadata: IsCellVisiblePreferenceKey.PreferenceValue? = nil + + + var body: some View { + ZStack { + GeometryReader { geometry in + Image(uiImage: thumbnail.image) + .resizable() + // .scaledToFit() + // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) +// .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in +// if isInView(innerRect: rect, isIn: outerProxy) { +// visibleMetadata = .init(isVisible: true, metadata: thumbnail.metadata) +// } else { +// visibleMetadata = .init(isVisible: false, metadata: thumbnail.metadata) +// } +// } +// .preference(key: IsCellVisiblePreferenceKey.self, value: visibleMetadata) + } + .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + + Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) + } + } +} + +struct IsCellVisiblePreferenceKey: PreferenceKey { + struct PreferenceValue: Equatable { + let isVisible: Bool + let metadata: tableMetadata + } + + typealias Value = PreferenceValue? + static var defaultValue: Value = nil + + static func reduce(value: inout Value, nextValue: () -> Value) { + value = value ?? nextValue() + } +} diff --git a/iOSClient/Media/Cell/NCMediaCellView.swift b/iOSClient/Media/Cell/NCMediaCellView.swift deleted file mode 100644 index 98793a4bd8..0000000000 --- a/iOSClient/Media/Cell/NCMediaCellView.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// NCMediaCellView.swift -// Nextcloud -// -// Created by Milen on 05.09.23. -// Copyright © 2023 Marino Faggiana. All rights reserved. -// - -import SwiftUI - -struct NCMediaCellView: View { - let thumbnail: ScaledThumbnail - let shrinkRatio: CGFloat - let outerProxy: GeometryProxy - @Binding var title: String - - var body: some View { - GeometryReader { geometry in - Image(uiImage: thumbnail.image) - .resizable() - // .scaledToFit() - // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in - if isInView(innerRect: rect, isIn: outerProxy) { - print(thumbnail.metadata.fileName) - title = thumbnail.metadata.fileName - } - } - } - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - } - - private func isInView(innerRect:CGRect, isIn outerProxy:GeometryProxy) -> Bool { - let innerOrigin = innerRect.origin.x - let imageWidth = innerRect.width - let scrollOrigin = outerProxy.frame(in: .global).origin.x - let scrollWidth = outerProxy.size.width - if innerOrigin + imageWidth < scrollOrigin + scrollWidth && innerOrigin + imageWidth > scrollOrigin || - innerOrigin + imageWidth > scrollOrigin && innerOrigin < scrollOrigin + scrollWidth { - return true - } - return false - } -} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 271688b907..b846c65e70 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -10,6 +10,7 @@ import SwiftUI import PreviewSnapshots import NextcloudKit import FlowGrid +import VisibilityTrackingScrollView class NCMediaUIHostingController: UIHostingController { required init?(coder aDecoder: NSCoder) { @@ -22,23 +23,20 @@ struct NCMediaNew: View { @State var columns = 2 @State var title = "" + @State private var visibleMetadatas: [String] = [] + public static let scrollViewCoordinateSpace = "scrollView" var body: some View { GeometryReader { outerProxy in ZStack(alignment: .top) { - ScrollView { + VisibilityTrackingScrollView(action: handleVisibilityChanged) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) -// .onChange(of: geometry.frame(in: .local)) { rect in -// if isInView(innerRect: rect, isIn: outerProxy) { -// print("WOW") -// } -// } + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) } } - }.coordinateSpace(name: NCMediaNew.scrollViewCoordinateSpace) + } HStack(content: { Text(title) @@ -48,6 +46,9 @@ struct NCMediaNew: View { .frame(maxWidth: .infinity) .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } + .onChange(of: visibleMetadatas) { metadata in + title = metadata[0] + } .onRotate { orientation in if orientation.isLandscapeHardCheck { columns = 6 @@ -58,9 +59,15 @@ struct NCMediaNew: View { } } + func handleVisibilityChanged(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { + DispatchQueue.main.async { + if let date = tracker.topVisibleView, !date.isEmpty { + title = date + } + } + } } - struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 232528e865..e79df6dbe5 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -8,6 +8,7 @@ import SwiftUI import PreviewSnapshots +import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] @@ -23,7 +24,7 @@ struct NCMediaRow: View { ProgressView() } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) } } } From aa0a1898ef08c024d6e0a3d32eb11cbc31f65801 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 13:42:47 +0200 Subject: [PATCH 014/103] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 29 -------------------------- iOSClient/Media/NCMediaNew.swift | 2 +- iOSClient/Media/NCMediaRow.swift | 3 +-- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 9005b3b39e..1c0ce61946 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -12,27 +12,12 @@ import VisibilityTrackingScrollView struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat - let outerProxy: GeometryProxy - @Binding var title: String -// @State private var visibleIndex: Set = [0,1] - @State private var visibleMetadata: IsCellVisiblePreferenceKey.PreferenceValue? = nil - var body: some View { ZStack { GeometryReader { geometry in Image(uiImage: thumbnail.image) .resizable() - // .scaledToFit() - // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) -// .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in -// if isInView(innerRect: rect, isIn: outerProxy) { -// visibleMetadata = .init(isVisible: true, metadata: thumbnail.metadata) -// } else { -// visibleMetadata = .init(isVisible: false, metadata: thumbnail.metadata) -// } -// } -// .preference(key: IsCellVisiblePreferenceKey.self, value: visibleMetadata) } .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) @@ -41,17 +26,3 @@ struct NCMediaCell: View { } } } - -struct IsCellVisiblePreferenceKey: PreferenceKey { - struct PreferenceValue: Equatable { - let isVisible: Bool - let metadata: tableMetadata - } - - typealias Value = PreferenceValue? - static var defaultValue: Value = nil - - static func reduce(value: inout Value, nextValue: () -> Value) { - value = value ?? nextValue() - } -} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index b846c65e70..4ca0ed559f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -33,7 +33,7 @@ struct NCMediaNew: View { VisibilityTrackingScrollView(action: handleVisibilityChanged) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) } } } diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index e79df6dbe5..0356329634 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -13,7 +13,6 @@ import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy - @Binding var title: String @StateObject private var viewModel = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -24,7 +23,7 @@ struct NCMediaRow: View { ProgressView() } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) } } } From 6526e65e40a9a8d9f51e92c3de203ac1e4dbf6e4 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 16:49:14 +0200 Subject: [PATCH 015/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 31 +++++++++++++------------- iOSClient/Media/NCMediaViewModel.swift | 6 +++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 4ca0ed559f..97b002091d 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -19,35 +19,34 @@ class NCMediaUIHostingController: UIHostingController { } struct NCMediaNew: View { - @StateObject private var viewModel = NCMediaViewModel() + @StateObject private var vm = NCMediaViewModel() @State var columns = 2 @State var title = "" - @State private var visibleMetadatas: [String] = [] - - public static let scrollViewCoordinateSpace = "scrollView" - var body: some View { GeometryReader { outerProxy in ZStack(alignment: .top) { VisibilityTrackingScrollView(action: handleVisibilityChanged) { LazyVStack(alignment: .leading, spacing: 2) { - ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) } + + if vm.needsLoadingMoreItems { + ProgressView() + .frame(maxWidth: .infinity) + .onAppear { vm.loadMoreItems() } + } } } - HStack(content: { - Text(title) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(.white) - }) - .frame(maxWidth: .infinity) - .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) - } - .onChange(of: visibleMetadatas) { metadata in - title = metadata[0] +// HStack(content: { +// Text(title) +// .font(.system(size: 20, weight: .bold)) +// .foregroundStyle(.white) +// }) +// .frame(maxWidth: .infinity) +// .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } .onRotate { orientation in if orientation.isLandscapeHardCheck { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index c74dcece2b..a55d4067ec 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -21,6 +21,8 @@ import NextcloudKit internal var filterClassTypeImage = false internal var filterClassTypeVideo = false + internal var needsLoadingMoreItems = true + init() { reloadDataSourceWithCompletion { _ in } @@ -86,6 +88,10 @@ import NextcloudKit break } } + + func loadMoreItems() { + + } } // MARK: Notifications From 1c64989d1221ee9a59eb13e01046993c5c29fdba Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 17:37:01 +0200 Subject: [PATCH 016/103] Reload when switching accounts Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 2 +- iOSClient/Media/NCMediaNew.swift | 27 +++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 1c0ce61946..cca622121e 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -22,7 +22,7 @@ struct NCMediaCell: View { .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) +// Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 97b002091d..6b3c466fc9 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -40,22 +40,23 @@ struct NCMediaNew: View { } } -// HStack(content: { -// Text(title) -// .font(.system(size: 20, weight: .bold)) -// .foregroundStyle(.white) -// }) -// .frame(maxWidth: .infinity) -// .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + HStack(content: { + Text(title) + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(.white) + }) + .frame(maxWidth: .infinity) + .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } - .onRotate { orientation in - if orientation.isLandscapeHardCheck { - columns = 6 - } else { - columns = 2 - } + } + .onRotate { orientation in + if orientation.isLandscapeHardCheck { + columns = 6 + } else { + columns = 2 } } + .onAppear { vm.reloadDataSourceWithCompletion {_ in } } } func handleVisibilityChanged(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { From c7cd3e0be696749c6554179cb612584c7f7d653a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 13 Sep 2023 18:15:49 +0200 Subject: [PATCH 017/103] Add shimmer and old and new media loading Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 32 ++--- iOSClient/Media/Cell/NCMediaCell.swift | 36 ++++- iOSClient/Media/NCMediaNew.swift | 9 +- iOSClient/Media/NCMediaRow.swift | 7 +- iOSClient/Media/NCMediaViewModel.swift | 183 +++++++++++++++++++++---- 5 files changed, 214 insertions(+), 53 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 6b3c6164da..45d9057c66 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -116,7 +116,6 @@ F343A4BF2A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C12A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; - F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F34624522AA08C5700FAA7B1 /* FlowGrid */; }; F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8682A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8692A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; @@ -136,6 +135,7 @@ F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */; }; F3E3B3BB2AA7396F006D08F5 /* NCMediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */; }; F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */; }; + F3EFBF5F2AB2100E00B5724C /* Shimmer in Frameworks */ = {isa = PBXBuildFile; productRef = F3EFBF5E2AB2100E00B5724C /* Shimmer */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -1499,7 +1499,7 @@ F76DA966277B76F30082465B /* UICKeyChainStore in Frameworks */, F7792DE529EEE02D005930CE /* MobileVLCKit.xcframework in Frameworks */, F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */, - F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */, + F3EFBF5F2AB2100E00B5724C /* Shimmer in Frameworks */, F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */, F76DA963277B760E0082465B /* Queuer in Frameworks */, F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, @@ -2901,8 +2901,8 @@ F7A1050D29E587AF00FFD92B /* TagListView */, F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, - F34624522AA08C5700FAA7B1 /* FlowGrid */, F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, + F3EFBF5E2AB2100E00B5724C /* Shimmer */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3078,8 +3078,8 @@ F31F69622A2F929600162F76 /* XCRemoteSwiftPackageReference "swiftui-preview-snapshots" */, F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, - F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */, F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, + F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4928,20 +4928,20 @@ version = 1.4.0; }; }; - F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */ = { + F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/rdev/FlowGrid.git"; + repositoryURL = "https://github.com/elegantchaos/VisibilityTrackingScrollView"; requirement = { kind = exactVersion; - version = 1.0.0; + version = 1.0.3; }; }; - F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */ = { + F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/elegantchaos/VisibilityTrackingScrollView"; + repositoryURL = "https://github.com/markiv/SwiftUI-Shimmer.git"; requirement = { - kind = exactVersion; - version = 1.0.3; + kind = upToNextMajorVersion; + minimumVersion = 1.4.0; }; }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { @@ -5236,16 +5236,16 @@ package = F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */; productName = SnapshotTestingHEIC; }; - F34624522AA08C5700FAA7B1 /* FlowGrid */ = { - isa = XCSwiftPackageProductDependency; - package = F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */; - productName = FlowGrid; - }; F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */ = { isa = XCSwiftPackageProductDependency; package = F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */; productName = VisibilityTrackingScrollView; }; + F3EFBF5E2AB2100E00B5724C /* Shimmer */ = { + isa = XCSwiftPackageProductDependency; + package = F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */; + productName = Shimmer; + }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index cca622121e..adf3a1b268 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -8,6 +8,7 @@ import SwiftUI import VisibilityTrackingScrollView +import Shimmer struct NCMediaCell: View { let thumbnail: ScaledThumbnail @@ -15,14 +16,35 @@ struct NCMediaCell: View { var body: some View { ZStack { - GeometryReader { geometry in - Image(uiImage: thumbnail.image) - .resizable() - } - .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + Image(uiImage: thumbnail.image) + .resizable() + .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) -// Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) +// Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) + } + } +} + +struct NCMediaLoadingCell: View { + let height: CGFloat + let itemsInRow: Int + let metadata: tableMetadata + + let gradient = Gradient(colors: [ + .black.opacity(0.4), + .black.opacity(0.7), + .black.opacity(0.4) + ]) + + var body: some View { + ZStack { + Image(uiImage: UIImage()) + .resizable() + .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "") + .frame(width: UIScreen.main.bounds.width / CGFloat(itemsInRow), height: 130) + .redacted(reason: .placeholder) + .shimmering(gradient: gradient, bandSize: 0.7) } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 6b3c466fc9..90e2dd1db3 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -9,7 +9,6 @@ import SwiftUI import PreviewSnapshots import NextcloudKit -import FlowGrid import VisibilityTrackingScrollView class NCMediaUIHostingController: UIHostingController { @@ -26,7 +25,7 @@ struct NCMediaNew: View { var body: some View { GeometryReader { outerProxy in ZStack(alignment: .top) { - VisibilityTrackingScrollView(action: handleVisibilityChanged) { + VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) @@ -45,7 +44,7 @@ struct NCMediaNew: View { .font(.system(size: 20, weight: .bold)) .foregroundStyle(.white) }) - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity ) .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } @@ -56,10 +55,10 @@ struct NCMediaNew: View { columns = 2 } } - .onAppear { vm.reloadDataSourceWithCompletion {_ in } } + .onAppear { vm.loadData() } } - func handleVisibilityChanged(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { + func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { DispatchQueue.main.async { if let date = tracker.topVisibleView, !date.isEmpty { title = date diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 0356329634..eab354668a 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -20,7 +20,11 @@ struct NCMediaRow: View { var body: some View { HStack(spacing: spacing) { if viewModel.rowData.scaledThumbnails.isEmpty { - ProgressView() + let randomHeight = CGFloat.random(in: 150...300) + + ForEach(metadatas, id: \.self) { metadata in + NCMediaLoadingCell(height: randomHeight, itemsInRow: metadatas.count, metadata: metadata) + } } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) @@ -33,4 +37,3 @@ struct NCMediaRow: View { } } } - diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index a55d4067ec..38ccb1c977 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -24,8 +24,6 @@ import NextcloudKit internal var needsLoadingMoreItems = true init() { - reloadDataSourceWithCompletion { _ in } - NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(moveFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) @@ -41,7 +39,7 @@ import NextcloudKit NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) } - @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { + func loadData() { guard let appDelegate, !appDelegate.account.isEmpty else { return } if account != appDelegate.account { @@ -52,7 +50,7 @@ import NextcloudKit self.queryDB(isForced: true) } - func queryDB(isForced: Bool = false) { + private func queryDB(isForced: Bool = false) { guard let appDelegate else { return } livePhoto = CCUtility.getLivePhoto() @@ -75,22 +73,25 @@ import NextcloudKit guard let predicate = predicate else { return } - metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) - - switch CCUtility.getMediaSortDate() { - case "date": - self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) - case "creationDate": - self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) - case "uploadDate": - self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) - default: - break + DispatchQueue.main.async { + self.metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) + + switch CCUtility.getMediaSortDate() { + case "date": + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) + case "creationDate": + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) + case "uploadDate": + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) + default: + break + } } } func loadMoreItems() { - + searchOldMedia() + searchNewMedia() } } @@ -98,7 +99,6 @@ import NextcloudKit extension NCMediaViewModel { @objc func deleteFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, let error = userInfo["error"] as? NKError else { return } let onlyLocalCache: Bool = userInfo["onlyLocalCache"] as? Bool ?? false @@ -124,7 +124,6 @@ extension NCMediaViewModel { } @objc func moveFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary? else { return } // if let hud = userInfo["hud"] as? JGProgressHUD { @@ -133,22 +132,19 @@ extension NCMediaViewModel { } @objc func copyFile(_ notification: NSNotification) { - moveFile(notification) } @objc func renameFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, let account = userInfo["account"] as? String, account == appDelegate?.account else { return } - self.reloadDataSourceWithCompletion { _ in } + self.loadData() } @objc func uploadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, let error = userInfo["error"] as? NKError, error == .success, @@ -156,6 +152,147 @@ extension NCMediaViewModel { account == appDelegate?.account else { return } - self.reloadDataSourceWithCompletion { _ in } + self.loadData() + } +} + +// MARK: - Search media + +extension NCMediaViewModel { + private func searchOldMedia(value: Int = -30, limit: Int = 300) { + +// if oldInProgress { return } else { oldInProgress = true } +// DispatchQueue.main.async { +// self.collectionView.reloadData() +// var bottom: CGFloat = 0 +// if let mainTabBar = self.tabBarController?.tabBar as? NCMainTabBar { +// bottom = -mainTabBar.getHight() +// } +// NCActivityIndicator.shared.start(backgroundView: self.view, bottom: bottom - 5, style: .medium) +// } + + var lessDate = Date() + if predicateDefault != nil { + if let metadata = NCManageDatabase.shared.getMetadata(predicate: predicateDefault!, sorted: "date", ascending: true) { + lessDate = metadata.date as Date + } + } + + var greaterDate: Date + if value == -999 { + greaterDate = Date.distantPast + } else { + greaterDate = Calendar.current.date(byAdding: .day, value: value, to: lessDate)! + } + + let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.searchMedia(path: mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in +// +//// self.oldInProgress = false +// DispatchQueue.main.async { +// NCActivityIndicator.shared.stop() +// self.loadData() +//// self.collectionView.reloadData() +// } + + if error == .success && account == self.appDelegate?.account { + if !files.isEmpty { + NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in + let predicateDate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) + let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateDate, self.predicateDefault!]) + let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) + let metadatasChanged = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) + if metadatasChanged.metadatasUpdate.isEmpty { + self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: true) + } else { + self.loadData() + } + } + } else { + self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: false) + } + } else if error != .success { + NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Media search old media error code \(error.errorCode) " + error.errorDescription) + } + } + } + + private func researchOldMedia(value: Int, limit: Int, withElseReloadDataSource: Bool) { + + if value == -30 { + searchOldMedia(value: -90) + } else if value == -90 { + searchOldMedia(value: -180) + } else if value == -180 { + searchOldMedia(value: -999) + } else if value == -999 && limit > 0 { + searchOldMedia(value: -999, limit: 0) + } else { + if withElseReloadDataSource { + loadData() + } + } + } + +// @objc func searchNewMediaTimer() { +// self.searchNewMedia() +// } +// + @objc func searchNewMedia() { + +// if newInProgress { return } else { +// newInProgress = true +// mediaCommandView?.activityIndicator.startAnimating() +// } + + var limit: Int = 1000 + guard var lessDate = Calendar.current.date(byAdding: .second, value: 1, to: Date()) else { return } + guard var greaterDate = Calendar.current.date(byAdding: .day, value: -30, to: Date()) else { return } + +// if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) { +// if let cell = visibleCells.first as? NCGridMediaCell { +// if cell.date != nil { +// if cell.date != self.metadatas.first?.date as Date? { +// lessDate = Calendar.current.date(byAdding: .second, value: 1, to: cell.date!)! +// limit = 0 +// } +// } +// } +// if let cell = visibleCells.last as? NCGridMediaCell { +// if cell.date != nil { +// greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: cell.date!)! +// } +// } +// } + +// reloadDataThenPerform { + + let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, data, error in + +// self.newInProgress = false +// DispatchQueue.main.async { +// self.mediaCommandView?.activityIndicator.stopAnimating() +// } + + if error == .success && account == self.appDelegate?.account && files.count > 0 { + NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in + let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) + let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.predicate!]) + let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) + let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) + if updateMetadatas.metadatasUpdate.count > 0 || updateMetadatas.metadatasDelete.count > 0 { + self.loadData() + } + } + } else if error == .success && files.count == 0 && self.metadatas.count == 0 { + self.searchOldMedia() + } else if error != .success { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) + } + } +// } } } From ecc4ef62271d0cff9a18361d49eadd3ca4895dc6 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 13 Sep 2023 18:47:36 +0200 Subject: [PATCH 018/103] Work on top section Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 9 +++++---- iOSClient/Media/NCMediaNew.swift | 23 ++++++++++++++++++----- iOSClient/Media/NCMediaRow.swift | 4 +--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index adf3a1b268..d5e6f81fbe 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -21,15 +21,16 @@ struct NCMediaCell: View { .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) -// Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) + // Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) } } } struct NCMediaLoadingCell: View { - let height: CGFloat let itemsInRow: Int let metadata: tableMetadata + let geometryProxy: GeometryProxy + let spacing: CGFloat let gradient = Gradient(colors: [ .black.opacity(0.4), @@ -41,8 +42,8 @@ struct NCMediaLoadingCell: View { ZStack { Image(uiImage: UIImage()) .resizable() - .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "") - .frame(width: UIScreen.main.bounds.width / CGFloat(itemsInRow), height: 130) + .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "")// TODO: Fix spacing + .frame(width: (geometryProxy.size.width - spacing) / CGFloat(itemsInRow), height: 130) .redacted(reason: .placeholder) .shimmering(gradient: gradient, bandSize: 0.7) } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 90e2dd1db3..b01bdd4e43 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -20,7 +20,7 @@ class NCMediaUIHostingController: UIHostingController { struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @State var columns = 2 - @State var title = "" + @State var title = "Placeholder" var body: some View { GeometryReader { outerProxy in @@ -37,14 +37,27 @@ struct NCMediaNew: View { .onAppear { vm.loadMoreItems() } } } + .padding(.top, 70) + .padding(.bottom, 30) } HStack(content: { - Text(title) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(.white) + HStack { + Text(title) + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(.white) + Spacer() + Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Text("Select") + }) + Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Image(systemName: "ellipsis") + }) + } }) - .frame(maxWidth: .infinity ) + .frame(maxWidth: .infinity) + .padding(.horizontal, 10) + .padding(.vertical, 20) .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index eab354668a..26b27ffc60 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -20,10 +20,8 @@ struct NCMediaRow: View { var body: some View { HStack(spacing: spacing) { if viewModel.rowData.scaledThumbnails.isEmpty { - let randomHeight = CGFloat.random(in: 150...300) - ForEach(metadatas, id: \.self) { metadata in - NCMediaLoadingCell(height: randomHeight, itemsInRow: metadatas.count, metadata: metadata) + NCMediaLoadingCell(itemsInRow: metadatas.count, metadata: metadata, geometryProxy: geometryProxy, spacing: spacing) } } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in From c2a43d70dc5822c2c6e10f37ac490068ca1e8db0 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 14 Sep 2023 15:08:48 +0200 Subject: [PATCH 019/103] Mark no thumbnails Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 33 ++++++++++++++++++----- iOSClient/Media/NCMediaRowViewModel.swift | 25 +++++++++-------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index d5e6f81fbe..ec2b06f16c 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -15,14 +15,35 @@ struct NCMediaCell: View { let shrinkRatio: CGFloat var body: some View { - ZStack { - Image(uiImage: thumbnail.image) - .resizable() - .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + var image = Image(uiImage: thumbnail.image) + .resizable() + .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + + ZStack(alignment: .bottomLeading) { + ZStack(alignment: .center) { + if thumbnail.isDefaultImage { + image + .foregroundColor(Color(uiColor: .systemGray4)) + .scaledToFit() + .frame(width: 40) + } else { + image + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) - // Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) + if thumbnail.metadata.isVideo { + Image(systemName: "play.fill") + .resizable() + .foregroundColor(Color(uiColor: .systemGray4)) + .scaledToFit() + .frame(width: 20) + .padding(.leading, 10) + .padding(.bottom, 10) + } } + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + .background(Color(uiColor: .systemGray6)) } } diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift index e507735c50..8b069c9a74 100644 --- a/iOSClient/Media/NCMediaRowViewModel.swift +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -16,6 +16,7 @@ struct RowData { struct ScaledThumbnail: Hashable { let image: UIImage + var isDefaultImage = false var scaledSize: CGSize = .zero let metadata: tableMetadata @@ -75,27 +76,29 @@ struct ScaledThumbnail: Hashable { sizeIcon: NCGlobal.shared.sizeIcon, etag: etagResource, options: options) { _, _, imageIcon, _, etag, error in + print(metadata.isVideo.description + " " + metadata.fileName) if error == .success, let image = imageIcon { NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) - DispatchQueue.main.async { - thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + } else { + thumbnails.append(ScaledThumbnail(image: UIImage(systemName: metadata.isVideo ? "video.fill" : "photo.fill")!.withRenderingMode(.alwaysTemplate), isDefaultImage: true, metadata: metadata)) + } - if thumbnails.count == self.metadatas.count { - thumbnails.enumerated().forEach { index, thumbnail in - thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) - } + DispatchQueue.main.async { + if thumbnails.count == self.metadatas.count { + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } - let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) - self.rowData.scaledThumbnails = thumbnails - self.rowData.shrinkRatio = shrinkRatio - } + self.rowData.scaledThumbnails = thumbnails + self.rowData.shrinkRatio = shrinkRatio } } } } } - } func getScaledThumbnailSize(of thumbnail: ScaledThumbnail, thumbnailsInRow thumbnails: [ScaledThumbnail]) -> CGSize { From 5cee6a6deabe516a5eabf72268b28aba3e21b68b Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 17:28:08 +0200 Subject: [PATCH 020/103] Add pull to refresh Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 17 ++++++ iOSClient/Main/Main.storyboard | 33 +----------- iOSClient/Media/Cell/NCMediaCell.swift | 29 ++++++++-- iOSClient/Media/NCMediaNew.swift | 54 +++++++++++++++++-- iOSClient/Media/NCMediaRow.swift | 13 ++--- iOSClient/Media/NCMediaViewModel.swift | 9 ++++ .../NCViewerMedia/NCViewerMediaPage.swift | 2 + 7 files changed, 112 insertions(+), 45 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 45d9057c66..d92aeb8d47 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; + F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6BEF2AB4AAB700148857 /* Refreshable */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -1502,6 +1503,7 @@ F3EFBF5F2AB2100E00B5724C /* Shimmer in Frameworks */, F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */, F76DA963277B760E0082465B /* Queuer in Frameworks */, + F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */, F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, F70B86752642CE3B00ED5349 /* FirebaseCrashlytics in Frameworks */, F7A1050E29E587AF00FFD92B /* TagListView in Frameworks */, @@ -2903,6 +2905,7 @@ F7F623B42A5EF4D30022D3D4 /* Gzip */, F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, F3EFBF5E2AB2100E00B5724C /* Shimmer */, + F30A6BEF2AB4AAB700148857 /* Refreshable */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3080,6 +3083,7 @@ F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, + F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4904,6 +4908,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/c-villain/Refreshable"; + requirement = { + kind = exactVersion; + version = 0.2.0; + }; + }; F31F694B2A2F6EFA00162F76 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; @@ -5151,6 +5163,11 @@ package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; productName = UICKeyChainStore; }; + F30A6BEF2AB4AAB700148857 /* Refreshable */ = { + isa = XCSwiftPackageProductDependency; + package = F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */; + productName = Refreshable; + }; F30A96332A2DFCD000D7BCFE /* Realm */ = { isa = XCSwiftPackageProductDependency; package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index 3e3f022757..557c9918c1 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -1,22 +1,12 @@ - + - + - - - - - - - - - - @@ -80,25 +70,6 @@ - - - - - - - - - - - - - - - - - - - diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index ec2b06f16c..5b5f84c1c6 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -9,13 +9,16 @@ import SwiftUI import VisibilityTrackingScrollView import Shimmer +import NextcloudKit struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat + let onTap: (ScaledThumbnail) -> Void + var body: some View { - var image = Image(uiImage: thumbnail.image) + let image = Image(uiImage: thumbnail.image) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") @@ -38,12 +41,22 @@ struct NCMediaCell: View { .foregroundColor(Color(uiColor: .systemGray4)) .scaledToFit() .frame(width: 20) - .padding(.leading, 10) - .padding(.bottom, 10) + .padding([.leading, .bottom], 10) } } .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) .background(Color(uiColor: .systemGray6)) + .onTapGesture { + onTap(thumbnail) + } + } +} + +struct NCMediaCell_Previews: PreviewProvider { + static var previews: some View { + let mockMetadata = tableMetadata() + + NCMediaCell(thumbnail: .init(image: UIImage(systemName: "video.fill")!, metadata: mockMetadata), shrinkRatio: 1, onTap: { _ in }) } } @@ -70,3 +83,13 @@ struct NCMediaLoadingCell: View { } } } + +struct NCMediaLoadingCell_Previews: PreviewProvider { + static var previews: some View { + let mockMetadata = tableMetadata() + + GeometryReader { proxy in + NCMediaLoadingCell(itemsInRow: 1, metadata: tableMetadata(), geometryProxy: proxy, spacing: 2) + } + } +} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index b01bdd4e43..46d42ade24 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -17,10 +17,44 @@ class NCMediaUIHostingController: UIHostingController { } } +struct NCViewerMediaPageController: UIViewControllerRepresentable { + let metadatas: [tableMetadata] + let selectedMetadata: tableMetadata + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UINavigationController { + + if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { + var index = 0 + for medatasImage in metadatas { + if medatasImage.ocId == selectedMetadata.ocId { + viewController.currentIndex = index + break + } + index += 1 + } + viewController.metadatas = metadatas + + return UINavigationController(rootViewController: viewController) + } else { + // Handle the case where the cast fails, such as returning a default view controller or showing an error. + // You can also return an empty UIViewController() as a fallback. + return UINavigationController() // Replace with your fallback or error handling logic + } + } + + func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) { + + } +} + struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() - @State var columns = 2 - @State var title = "Placeholder" + @State private var columns = 2 + @State private var title = "" + + @State private var isMediaViewControllerPresented = false + + @State private var selectedMetadata = tableMetadata() var body: some View { GeometryReader { outerProxy in @@ -28,7 +62,10 @@ struct NCMediaNew: View { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) { tappedThumbnail in + selectedMetadata = tappedThumbnail.metadata + isMediaViewControllerPresented = true + } } if vm.needsLoadingMoreItems { @@ -39,6 +76,10 @@ struct NCMediaNew: View { } .padding(.top, 70) .padding(.bottom, 30) + + } + .refreshable { + vm.onPullToRefresh() } HStack(content: { @@ -47,10 +88,10 @@ struct NCMediaNew: View { .font(.system(size: 20, weight: .bold)) .foregroundStyle(.white) Spacer() - Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Button(action: {}, label: { Text("Select") }) - Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Button(action: {}, label: { Image(systemName: "ellipsis") }) } @@ -69,6 +110,9 @@ struct NCMediaNew: View { } } .onAppear { vm.loadData() } + .fullScreenCover(isPresented: $isMediaViewControllerPresented) { + NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: selectedMetadata) + } } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 26b27ffc60..ee312d7852 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -13,25 +13,26 @@ import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy + let onCellTap: (ScaledThumbnail) -> Void - @StateObject private var viewModel = NCMediaRowViewModel() + @StateObject private var vm = NCMediaRowViewModel() private let spacing: CGFloat = 2 var body: some View { HStack(spacing: spacing) { - if viewModel.rowData.scaledThumbnails.isEmpty { + if vm.rowData.scaledThumbnails.isEmpty { ForEach(metadatas, id: \.self) { metadata in NCMediaLoadingCell(itemsInRow: metadatas.count, metadata: metadata, geometryProxy: geometryProxy, spacing: spacing) } } else { - ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) + ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, onTap: onCellTap) } } } .onAppear { - viewModel.configure(metadatas: metadatas) - viewModel.downloadThumbnails(rowWidth: geometryProxy.size.width, spacing: spacing) + vm.configure(metadatas: metadatas) + vm.downloadThumbnails(rowWidth: geometryProxy.size.width, spacing: spacing) } } } diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 38ccb1c977..d594b6b8b4 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -29,6 +29,8 @@ import NextcloudKit NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) + + searchNewMedia() } deinit { @@ -91,8 +93,15 @@ import NextcloudKit func loadMoreItems() { searchOldMedia() + } + + func onPullToRefresh() { searchNewMedia() } + + func onCellTapped(metadata: tableMetadata) { + appDelegate?.activeServerUrl = metadata.serverUrl + } } // MARK: Notifications diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 42e672ae80..efc6d7cff4 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -71,6 +71,8 @@ class NCViewerMediaPage: UIViewController { private lazy var moreNavigationItem = UIBarButtonItem(image: UIImage(named: "more")!.image(color: .label, size: 25), style: .plain, target: self, action: #selector(openMenuMore)) private lazy var imageDetailNavigationItem = UIBarButtonItem(image: UIImage(systemName: "info.circle")!.image(color: .label, size: 22), style: .plain, target: self, action: #selector(toggleDetail)) + var dismissAction: (() -> Void)? + // MARK: - View Life Cycle override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { From 5e4a83faf24984128dcc66a840c30354490dc451 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 18:00:04 +0200 Subject: [PATCH 021/103] Add animated color change on top bar Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 16 ++++++++++++++ iOSClient/Media/NCMediaNew.swift | 29 +++++++++++++++----------- iOSClient/Utility/PreferenceKeys.swift | 16 ++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 iOSClient/Utility/PreferenceKeys.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index d92aeb8d47..e4079662ee 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -77,6 +77,13 @@ D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6BEF2AB4AAB700148857 /* Refreshable */; }; + F30A6BF22AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF32AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF42AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF52AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF62AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF72AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF82AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -837,6 +844,7 @@ C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesIntegrationTests.swift; sourceTree = ""; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; + F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceKeys.swift; sourceTree = ""; }; F30A96042A27299D00D7BCFE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = EnvVars.stencil; sourceTree = ""; }; F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EnvVars.generated.swift; path = Sourcery/EnvVars.generated.swift; sourceTree = ""; }; @@ -2257,6 +2265,7 @@ F702F2FC25EE5D2C008F8E80 /* NYMnemonic */, AF36077027BFA4E8001A243D /* ParallelWorker.swift */, F7245923289BB50B00474787 /* ThreadSafeDictionary.swift */, + F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */, ); path = Utility; sourceTree = ""; @@ -3361,6 +3370,7 @@ D575039F27146F93008DC9DC /* String+Extension.swift in Sources */, F769CA1A2966EA3C00039397 /* ComponentView.swift in Sources */, F757CC8829E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, + F30A6BF82AB4B31200148857 /* PreferenceKeys.swift in Sources */, F79B646326CA661600838ACA /* UIControl+Extension.swift in Sources */, F78A10C429322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */, F75CA1482962F13700B01130 /* HUDView.swift in Sources */, @@ -3429,6 +3439,7 @@ F7490E6E29882B56009DCE94 /* NCBrand.swift in Sources */, F7490E8129882C79009DCE94 /* NCManageDatabase+DashboardWidget.swift in Sources */, F7490E8629882C99009DCE94 /* NCUtilityFileSystem.swift in Sources */, + F30A6BF72AB4B31200148857 /* PreferenceKeys.swift in Sources */, F763D2A22A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */, F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */, F7490E8529882C8C009DCE94 /* NCManageDatabase+Video.swift in Sources */, @@ -3495,6 +3506,7 @@ F7F878AF1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */, AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */, F702F2D025EE5B5C008F8E80 /* NCGlobal.swift in Sources */, + F30A6BF52AB4B31200148857 /* PreferenceKeys.swift in Sources */, F343A4BE2A1E734600DDA874 /* Optional+Extension.swift in Sources */, F7EDE4DB262D7BA200414FE6 /* NCCellProtocol.swift in Sources */, F72944F62A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */, @@ -3569,6 +3581,7 @@ F77ED59328C9CEA000E24ED0 /* ToolbarWidgetProvider.swift in Sources */, F72A17D828B221E300F3F159 /* DashboardWidgetView.swift in Sources */, F77ED59528C9CEA400E24ED0 /* ToolbarWidgetView.swift in Sources */, + F30A6BF32AB4B31200148857 /* PreferenceKeys.swift in Sources */, F78302FB28B4C3EE00B84583 /* NCManageDatabase+Video.swift in Sources */, F72EA95228B7BA2A00C88F0C /* DashboardWidgetProvider.swift in Sources */, F343A4BC2A1E734600DDA874 /* Optional+Extension.swift in Sources */, @@ -3611,6 +3624,7 @@ F7D68FCF28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */, F359D86B2A7D03420023F405 /* NCUtility+Exif.swift in Sources */, F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */, + F30A6BF62AB4B31200148857 /* PreferenceKeys.swift in Sources */, AF4BF61B27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, F70460542499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */, F78A10C329322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */, @@ -3678,6 +3692,7 @@ F769454622E9F1B0000A798A /* NCShareCommon.swift in Sources */, F70753F12542A9A200972D44 /* NCViewerMedia.swift in Sources */, F78A18B823CDE2B300F681F3 /* NCViewerRichWorkspace.swift in Sources */, + F30A6BF22AB4B31200148857 /* PreferenceKeys.swift in Sources */, F7A60F86292D215000FCE1F2 /* NCShareAccounts.swift in Sources */, F77910AB25DD53C700CEDB9E /* NCSettingsBundleHelper.swift in Sources */, AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, @@ -3853,6 +3868,7 @@ F343A4BD2A1E734600DDA874 /* Optional+Extension.swift in Sources */, F7A8D73528F17E16008BBE1C /* NCManageDatabase.swift in Sources */, F7A8D74428F1827B008BBE1C /* ThreadSafeDictionary.swift in Sources */, + F30A6BF42AB4B31200148857 /* PreferenceKeys.swift in Sources */, F7C9739528F17131002C43E2 /* IntentHandler.swift in Sources */, F7A8D73D28F181D3008BBE1C /* NCUtilityFileSystem.swift in Sources */, F7A8D74528F1828E008BBE1C /* CCUtility.m in Sources */, diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 46d42ade24..97fcc7ef66 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -36,24 +36,19 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { return UINavigationController(rootViewController: viewController) } else { - // Handle the case where the cast fails, such as returning a default view controller or showing an error. - // You can also return an empty UIViewController() as a fallback. - return UINavigationController() // Replace with your fallback or error handling logic + return UINavigationController() } } - func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) { - - } + func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) {} } struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @State private var columns = 2 @State private var title = "" - + @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false - @State private var selectedMetadata = tableMetadata() var body: some View { @@ -76,17 +71,27 @@ struct NCMediaNew: View { } .padding(.top, 70) .padding(.bottom, 30) + .background(GeometryReader { geometry in + Color.clear + .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) + }) + .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in + withAnimation(.easeInOut) { + isScrolledToTop = value.y >= 0 + } + } } .refreshable { vm.onPullToRefresh() } + .coordinateSpace(name: "scroll") HStack(content: { HStack { Text(title) .font(.system(size: 20, weight: .bold)) - .foregroundStyle(.white) + .foregroundStyle(isScrolledToTop ? .black : .white) Spacer() Button(action: {}, label: { Text("Select") @@ -97,9 +102,9 @@ struct NCMediaNew: View { } }) .frame(maxWidth: .infinity) - .padding(.horizontal, 10) - .padding(.vertical, 20) - .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + .padding([.horizontal, .top], 10) + .padding(.bottom, 20) + .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } .onRotate { orientation in diff --git a/iOSClient/Utility/PreferenceKeys.swift b/iOSClient/Utility/PreferenceKeys.swift new file mode 100644 index 0000000000..5ddb42b507 --- /dev/null +++ b/iOSClient/Utility/PreferenceKeys.swift @@ -0,0 +1,16 @@ +// +// PreferenceKeys.swift +// Nextcloud +// +// Created by Milen on 15.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import SwiftUI + +struct ScrollOffsetPreferenceKey: PreferenceKey { + static var defaultValue: CGPoint = .zero + + static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {} +} From 0cf382b3b25732d295cdd7cd8231627ac061818d Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 18:05:52 +0200 Subject: [PATCH 022/103] Adjust Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 97fcc7ef66..0097e85af3 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -77,7 +77,7 @@ struct NCMediaNew: View { }) .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in withAnimation(.easeInOut) { - isScrolledToTop = value.y >= 0 + isScrolledToTop = value.y >= -10 } } From 366461dff1285878053e19ca58aaac6550bafbd9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 18:12:38 +0200 Subject: [PATCH 023/103] Adjust Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 3 ++- iOSClient/Media/NCMediaViewModel.swift | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 0097e85af3..52781c129f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -67,10 +67,11 @@ struct NCMediaNew: View { ProgressView() .frame(maxWidth: .infinity) .onAppear { vm.loadMoreItems() } + .padding(.top, 10) } } .padding(.top, 70) - .padding(.bottom, 30) + .padding(.bottom, 40) .background(GeometryReader { geometry in Color.clear .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index d594b6b8b4..b4a824bfdd 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -93,6 +93,7 @@ import NextcloudKit func loadMoreItems() { searchOldMedia() +// needsLoadingMoreItems = false } func onPullToRefresh() { From 64bc0a6b456d1387db9f22cfae90a52d92b433aa Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 18 Sep 2023 12:34:00 +0200 Subject: [PATCH 024/103] Add actions to some options Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 2 +- iOSClient/Media/Cell/NCMediaCell.swift | 5 ++++ iOSClient/Media/NCMediaNew.swift | 40 ++++++++++++++++++++++++-- iOSClient/Media/NCMediaViewModel.swift | 12 ++++++-- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index e4079662ee..0f97c8a052 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -2395,8 +2395,8 @@ children = ( F720B5B72507B9A5008C94E5 /* Cell */, F7501C302212E57400FB1415 /* NCMedia.storyboard */, - F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, + F7501C312212E57400FB1415 /* NCMedia.swift */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */, F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */, diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 5b5f84c1c6..cb10e291f3 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -21,6 +21,11 @@ struct NCMediaCell: View { let image = Image(uiImage: thumbnail.image) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + .contextMenu(ContextMenu(menuItems: { + Text("Menu Item 1") + Text("Menu Item 2") + Text("Menu Item 3") + })) ZStack(alignment: .bottomLeading) { ZStack(alignment: .center) { diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 52781c129f..b0110ed240 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -50,6 +50,7 @@ struct NCMediaNew: View { @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false @State private var selectedMetadata = tableMetadata() + @State private var sort: Int = 0 var body: some View { GeometryReader { outerProxy in @@ -97,9 +98,44 @@ struct NCMediaNew: View { Button(action: {}, label: { Text("Select") }) - Button(action: {}, label: { + Menu { + Section { + Button(action: { + vm.filterClassTypeImage = !vm.filterClassTypeImage + vm.filterClassTypeVideo = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") + }) + Button(action: { + vm.filterClassTypeVideo = !vm.filterClassTypeVideo + vm.filterClassTypeImage = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + }) + } + + Section { + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } + + Picker("Sorting options", selection: $sort) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + } + .pickerStyle(.menu) + + } label: { Image(systemName: "ellipsis") - }) + } } }) .frame(maxWidth: .infinity) diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index b4a824bfdd..33a5d0e2d2 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -7,6 +7,7 @@ // import NextcloudKit +import Combine @MainActor class NCMediaViewModel: ObservableObject { @Published var metadatas: [tableMetadata] = [] @@ -18,8 +19,10 @@ import NextcloudKit private var predicateDefault: NSPredicate? private var predicate: NSPredicate? private let appDelegate = UIApplication.shared.delegate as? AppDelegate - internal var filterClassTypeImage = false - internal var filterClassTypeVideo = false + @Published internal var filterClassTypeImage = false + @Published internal var filterClassTypeVideo = false + + private var cancellables: Set = [] internal var needsLoadingMoreItems = true @@ -31,6 +34,9 @@ import NextcloudKit NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) searchNewMedia() + + $filterClassTypeImage.sink { _ in self.loadData() }.store(in: &cancellables) + $filterClassTypeVideo.sink{ _ in self.loadData() }.store(in: &cancellables) } deinit { @@ -93,7 +99,7 @@ import NextcloudKit func loadMoreItems() { searchOldMedia() -// needsLoadingMoreItems = false + needsLoadingMoreItems = false } func onPullToRefresh() { From 32280faad6685f08878227b623cbe6e8f371cb4e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 18 Sep 2023 15:47:41 +0200 Subject: [PATCH 025/103] Design work Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index b0110ed240..0411dae1b1 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -46,7 +46,7 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @State private var columns = 2 - @State private var title = "" + @State private var title = "Media" @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false @State private var selectedMetadata = tableMetadata() @@ -93,11 +93,16 @@ struct NCMediaNew: View { HStack { Text(title) .font(.system(size: 20, weight: .bold)) - .foregroundStyle(isScrolledToTop ? .black : .white) + .foregroundStyle(isScrolledToTop ? Color.primary : .white) Spacer() Button(action: {}, label: { - Text("Select") + Text("Select").font(.system(size: 14)) + .foregroundStyle(isScrolledToTop ? .blue : .white) }) + .padding(.horizontal, 6) + .padding(.vertical, 3) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) Menu { Section { Button(action: { @@ -132,10 +137,15 @@ struct NCMediaNew: View { Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) } .pickerStyle(.menu) - } label: { - Image(systemName: "ellipsis") - } + Image(systemName: "ellipsis").font(.system(size: 15)) + .padding(.horizontal, 2) + .padding(.vertical, 8) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) + .foregroundColor(isScrolledToTop ? Color.blue : .white) + + } } }) .frame(maxWidth: .infinity) From 887988bdf10429b632a03203c601ece896de4ac4 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 12:35:11 +0200 Subject: [PATCH 026/103] WIP with pushing VCs (kill me) Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 90 +++++---- iOSClient/Media/NCMediaNew.swift | 265 +++++++++++++++---------- iOSClient/Media/NCMediaRow.swift | 6 +- 3 files changed, 208 insertions(+), 153 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index cb10e291f3..075291252e 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -14,8 +14,9 @@ import NextcloudKit struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat - - let onTap: (ScaledThumbnail) -> Void + @Binding var isInSelectMode: Bool + let onTap: (ScaledThumbnail, Bool) -> Void + @State private var isTappedInSelectMode = false var body: some View { let image = Image(uiImage: thumbnail.image) @@ -27,42 +28,57 @@ struct NCMediaCell: View { Text("Menu Item 3") })) - ZStack(alignment: .bottomLeading) { - ZStack(alignment: .center) { - if thumbnail.isDefaultImage { - image + ZStack(alignment: .center) { +// NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { + if thumbnail.isDefaultImage { + image + .foregroundColor(Color(uiColor: .systemGray4)) + .scaledToFit() + .frame(width: 40) + } else { + image + } +// } + + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .overlay(alignment: .bottomLeading) { + if thumbnail.metadata.isVideo, !thumbnail.isDefaultImage { + Image(systemName: "play.fill") + .resizable() .foregroundColor(Color(uiColor: .systemGray4)) .scaledToFit() - .frame(width: 40) - } else { - image + .frame(width: 20) + .padding([.leading, .bottom], 10) } } - .frame(maxWidth: .infinity, maxHeight: .infinity) - - if thumbnail.metadata.isVideo { - Image(systemName: "play.fill") - .resizable() - .foregroundColor(Color(uiColor: .systemGray4)) - .scaledToFit() - .frame(width: 20) - .padding([.leading, .bottom], 10) + .overlay { + if isInSelectMode, isTappedInSelectMode { + Color.black.opacity(0.6).frame(maxWidth: .infinity) + } + } + .overlay(alignment: .bottomTrailing) { + if isInSelectMode, isTappedInSelectMode { + Image(systemName: "checkmark.circle.fill") + .resizable() + .foregroundColor(.blue) + .background(.white) + .clipShape(Circle()) + .scaledToFit() + .frame(width: 20) + .padding([.trailing, .bottom], 10) + } + } + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + .background(Color(uiColor: .systemGray6)) + .onTapGesture { + if isInSelectMode { isTappedInSelectMode.toggle() } + onTap(thumbnail, isTappedInSelectMode) + } + .onChange(of: isInSelectMode) { newValue in + isTappedInSelectMode = !newValue } } - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - .background(Color(uiColor: .systemGray6)) - .onTapGesture { - onTap(thumbnail) - } - } -} - -struct NCMediaCell_Previews: PreviewProvider { - static var previews: some View { - let mockMetadata = tableMetadata() - - NCMediaCell(thumbnail: .init(image: UIImage(systemName: "video.fill")!, metadata: mockMetadata), shrinkRatio: 1, onTap: { _ in }) - } } struct NCMediaLoadingCell: View { @@ -88,13 +104,3 @@ struct NCMediaLoadingCell: View { } } } - -struct NCMediaLoadingCell_Previews: PreviewProvider { - static var previews: some View { - let mockMetadata = tableMetadata() - - GeometryReader { proxy in - NCMediaLoadingCell(itemsInRow: 1, metadata: tableMetadata(), geometryProxy: proxy, spacing: 2) - } - } -} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 0411dae1b1..d30e61fbff 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -11,9 +11,35 @@ import PreviewSnapshots import NextcloudKit import VisibilityTrackingScrollView -class NCMediaUIHostingController: UIHostingController { +protocol DataDelegate: AnyObject { + func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) +} + +class NCMediaUIHostingController: UIHostingController, DataDelegate { required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder, rootView: NCMediaNew()) + let view = NCMediaNew() + super.init(coder: aDecoder, rootView: view) + rootView.dataModelDelegate = self + } + + func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) { + if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { + var index = 0 + for medatasImage in metadatas { + if medatasImage.ocId == selectedMetadata.ocId { + viewController.currentIndex = index + break + } + index += 1 + } + viewController.metadatas = metadatas + +// NCViewer.shared.view(viewController: UINavigationController(rootViewController: self), metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) + +// let navController = UINavigationController(rootViewController: self) +// self.navigationController!.pushViewController(viewController, animated: true) + self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) + } } } @@ -21,7 +47,7 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { let metadatas: [tableMetadata] let selectedMetadata: tableMetadata - func makeUIViewController(context: UIViewControllerRepresentableContext) -> UINavigationController { + func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { var index = 0 @@ -34,13 +60,13 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { } viewController.metadatas = metadatas - return UINavigationController(rootViewController: viewController) + return viewController } else { - return UINavigationController() + return NCViewerMediaPage() } } - func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) {} + func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} } struct NCMediaNew: View { @@ -49,121 +75,144 @@ struct NCMediaNew: View { @State private var title = "Media" @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false - @State private var selectedMetadata = tableMetadata() + @State private var tappedMetadata = tableMetadata() @State private var sort: Int = 0 + @State private var isInSelectMode = false + @State private var selectedMetadataInSelectMode: [tableMetadata] = [] + + weak var dataModelDelegate: DataDelegate? var body: some View { - GeometryReader { outerProxy in - ZStack(alignment: .top) { - VisibilityTrackingScrollView(action: cellVisibilityDidChange) { - LazyVStack(alignment: .leading, spacing: 2) { - ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) { tappedThumbnail in - selectedMetadata = tappedThumbnail.metadata - isMediaViewControllerPresented = true + NavigationView { + GeometryReader { outerProxy in + ZStack(alignment: .top) { + VisibilityTrackingScrollView(action: cellVisibilityDidChange) { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, tappedInSelectMode in + + if tappedInSelectMode { + selectedMetadataInSelectMode.append(tappedThumbnail.metadata) + } else { + selectedMetadataInSelectMode.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) + } + + print(selectedMetadataInSelectMode) + + if !isInSelectMode { + tappedMetadata = tappedThumbnail.metadata + dataModelDelegate?.updateData(metadatas: vm.metadatas, selectedMetadata: tappedMetadata, image: tappedThumbnail.image) +// isMediaViewControllerPresented = true + } + } } - } - if vm.needsLoadingMoreItems { - ProgressView() - .frame(maxWidth: .infinity) - .onAppear { vm.loadMoreItems() } - .padding(.top, 10) - } - } - .padding(.top, 70) - .padding(.bottom, 40) - .background(GeometryReader { geometry in - Color.clear - .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) - }) - .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in - withAnimation(.easeInOut) { - isScrolledToTop = value.y >= -10 + if vm.needsLoadingMoreItems { + ProgressView() + .frame(maxWidth: .infinity) + .onAppear { vm.loadMoreItems() } + .padding(.top, 10) + } } - } - - } - .refreshable { - vm.onPullToRefresh() - } - .coordinateSpace(name: "scroll") - - HStack(content: { - HStack { - Text(title) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(isScrolledToTop ? Color.primary : .white) - Spacer() - Button(action: {}, label: { - Text("Select").font(.system(size: 14)) - .foregroundStyle(isScrolledToTop ? .blue : .white) + .padding(.top, 70) + .padding(.bottom, 40) + .background(GeometryReader { geometry in + Color.clear + .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) }) - .padding(.horizontal, 6) - .padding(.vertical, 3) - .background(.ultraThinMaterial) - .cornerRadius(.infinity) - Menu { - Section { - Button(action: { - vm.filterClassTypeImage = !vm.filterClassTypeImage - vm.filterClassTypeVideo = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") - }) - Button(action: { - vm.filterClassTypeVideo = !vm.filterClassTypeVideo - vm.filterClassTypeImage = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") - }) - } - - Section { - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") - }) - } - - Picker("Sorting options", selection: $sort) { - Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) - Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) - Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in + withAnimation(.easeInOut) { + isScrolledToTop = value.y >= -10 } - .pickerStyle(.menu) - } label: { - Image(systemName: "ellipsis").font(.system(size: 15)) - .padding(.horizontal, 2) - .padding(.vertical, 8) - .background(.ultraThinMaterial) - .cornerRadius(.infinity) - .foregroundColor(isScrolledToTop ? Color.blue : .white) + } - } } - }) - .frame(maxWidth: .infinity) - .padding([.horizontal, .top], 10) - .padding(.bottom, 20) - .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + .refreshable { + vm.onPullToRefresh() + } + .coordinateSpace(name: "scroll") + + HStack(content: { + HStack { + Text(title) + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(isScrolledToTop ? Color.primary : .white) + Spacer() + Button(action: { + isInSelectMode.toggle() + }, label: { + Text(NSLocalizedString(isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) + .foregroundStyle(isScrolledToTop ? .blue : .white) + }) + .padding(.horizontal, 6) + .padding(.vertical, 3) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) + + if !isInSelectMode { + Menu { + Section { + Button(action: { + vm.filterClassTypeImage = !vm.filterClassTypeImage + vm.filterClassTypeVideo = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") + }) + Button(action: { + vm.filterClassTypeVideo = !vm.filterClassTypeVideo + vm.filterClassTypeImage = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + }) + } + + Section { + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } + + Picker("Sorting options", selection: $sort) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + } + .pickerStyle(.menu) + } label: { + Image(systemName: "ellipsis").font(.system(size: 15)) + .padding(.horizontal, 2) + .padding(.vertical, 8) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) + .foregroundColor(isScrolledToTop ? Color.blue : .white) + + } + } + } + }) + .frame(maxWidth: .infinity) + .padding([.horizontal, .top], 10) + .padding(.bottom, 20) + .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + } } - } - .onRotate { orientation in - if orientation.isLandscapeHardCheck { - columns = 6 - } else { - columns = 2 + .onRotate { orientation in + if orientation.isLandscapeHardCheck { + columns = 6 + } else { + columns = 2 + } } + .onAppear { vm.loadData() } + .fullScreenCover(isPresented: $isMediaViewControllerPresented) { + NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) } - .onAppear { vm.loadData() } - .fullScreenCover(isPresented: $isMediaViewControllerPresented) { - NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: selectedMetadata) } } diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index ee312d7852..c0add6f476 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -13,8 +13,8 @@ import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy - let onCellTap: (ScaledThumbnail) -> Void - + @Binding var isInSelectMode: Bool + let onCellTap: (ScaledThumbnail, Bool) -> Void @StateObject private var vm = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -26,7 +26,7 @@ struct NCMediaRow: View { } } else { ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, onTap: onCellTap) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onTap: onCellTap) } } } From 6f9d6227d7fb67cc8799ab6204f16d8d252afd59 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:01:20 +0200 Subject: [PATCH 027/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 4 ++-- iOSClient/Media/NCMediaNew.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 075291252e..4b118f59f9 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -29,7 +29,7 @@ struct NCMediaCell: View { })) ZStack(alignment: .center) { -// NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { + NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { if thumbnail.isDefaultImage { image .foregroundColor(Color(uiColor: .systemGray4)) @@ -38,7 +38,7 @@ struct NCMediaCell: View { } else { image } -// } + } } .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index d30e61fbff..74b1622045 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -212,8 +212,8 @@ struct NCMediaNew: View { .onAppear { vm.loadData() } .fullScreenCover(isPresented: $isMediaViewControllerPresented) { NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) - } - } + } + }.navigationBarHidden(true) } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { From f755cfe3d0377678ec97847560079ffe24b5de09 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:10:10 +0200 Subject: [PATCH 028/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 74b1622045..347426fca2 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -34,11 +34,11 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate } viewController.metadatas = metadatas -// NCViewer.shared.view(viewController: UINavigationController(rootViewController: self), metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) - + NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) + // let navController = UINavigationController(rootViewController: self) // self.navigationController!.pushViewController(viewController, animated: true) - self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) +// self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) } } } @@ -213,7 +213,7 @@ struct NCMediaNew: View { .fullScreenCover(isPresented: $isMediaViewControllerPresented) { NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) } - }.navigationBarHidden(true) + } } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { From 1bc1b848f0a4b64c39aff8d34eda4fb672962f2a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:10:22 +0200 Subject: [PATCH 029/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 347426fca2..8f38d3163f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -43,30 +43,30 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate } } -struct NCViewerMediaPageController: UIViewControllerRepresentable { - let metadatas: [tableMetadata] - let selectedMetadata: tableMetadata - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { - - if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { - var index = 0 - for medatasImage in metadatas { - if medatasImage.ocId == selectedMetadata.ocId { - viewController.currentIndex = index - break - } - index += 1 - } - viewController.metadatas = metadatas - - return viewController - } else { - return NCViewerMediaPage() - } - } - - func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} +//struct NCViewerMediaPageController: UIViewControllerRepresentable { +// let metadatas: [tableMetadata] +// let selectedMetadata: tableMetadata +// +// func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { +// +// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { +// var index = 0 +// for medatasImage in metadatas { +// if medatasImage.ocId == selectedMetadata.ocId { +// viewController.currentIndex = index +// break +// } +// index += 1 +// } +// viewController.metadatas = metadatas +// +// return viewController +// } else { +// return NCViewerMediaPage() +// } +// } +// +// func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} } struct NCMediaNew: View { From fcbb6e352a24bb9c85340b331ca83ed4f4015552 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:10:43 +0200 Subject: [PATCH 030/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 8f38d3163f..ce5ff88b37 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -67,7 +67,7 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate // } // // func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} -} +//} struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() From 1812bfbc35e33d3de1f2178132c78a7293ae785e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:11:36 +0200 Subject: [PATCH 031/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index ce5ff88b37..347426fca2 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -43,31 +43,31 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate } } -//struct NCViewerMediaPageController: UIViewControllerRepresentable { -// let metadatas: [tableMetadata] -// let selectedMetadata: tableMetadata -// -// func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { -// -// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { -// var index = 0 -// for medatasImage in metadatas { -// if medatasImage.ocId == selectedMetadata.ocId { -// viewController.currentIndex = index -// break -// } -// index += 1 -// } -// viewController.metadatas = metadatas -// -// return viewController -// } else { -// return NCViewerMediaPage() -// } -// } -// -// func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} -//} +struct NCViewerMediaPageController: UIViewControllerRepresentable { + let metadatas: [tableMetadata] + let selectedMetadata: tableMetadata + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { + + if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { + var index = 0 + for medatasImage in metadatas { + if medatasImage.ocId == selectedMetadata.ocId { + viewController.currentIndex = index + break + } + index += 1 + } + viewController.metadatas = metadatas + + return viewController + } else { + return NCViewerMediaPage() + } + } + + func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} +} struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() From c65c5dd1e9f51680fe8fe8f7f9e7b74a2d9ea12d Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 19 Sep 2023 15:26:27 +0200 Subject: [PATCH 032/103] move rightBarButtonItems in viewWillAppear --- .../Viewer/NCViewerMedia/NCViewerMediaPage.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index efc6d7cff4..f10ab06d23 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -128,12 +128,6 @@ class NCViewerMediaPage: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil) - - if currentViewController.metadata.isImage { - navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] - } else { - navigationItem.rightBarButtonItems = [moreNavigationItem] - } } deinit { @@ -156,6 +150,16 @@ class NCViewerMediaPage: UIViewController { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil) } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if currentViewController.metadata.isImage { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] + } else { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] + } + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) From fc9d15abdd360e4713350159763688043b800233 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:47:08 +0200 Subject: [PATCH 033/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 1 + .../NCViewerMediaPage.storyboard | 6 +++--- .../NCViewerMedia/NCViewerMediaPage.swift | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 347426fca2..31bf0e5f58 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -59,6 +59,7 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { index += 1 } viewController.metadatas = metadatas + viewController.hidesBottomBarWhenPushed = true return viewController } else { diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard index ba071d548f..49f376b517 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard @@ -1,9 +1,9 @@ - + - + @@ -12,7 +12,7 @@ - + diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index efc6d7cff4..c93945ce86 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -87,6 +87,25 @@ class NCViewerMediaPage: UIViewController { viewerMediaScreenMode = .normal } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if currentViewController.metadata.isImage { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] + } else { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] + } + + self.tabBarController?.tabBar.isHidden = true + + view.setNeedsLayout() + view.layoutIfNeeded() + } + + override func viewWillDisappear(_ animated: Bool) { + self.tabBarController?.tabBar.isHidden = false + } + override func viewDidLoad() { super.viewDidLoad() From 3459e8d47b1298c345874d78d4c8dd52e91544ec Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 19 Sep 2023 16:03:04 +0200 Subject: [PATCH 034/103] Update NCViewerMediaPage.swift --- .../NCViewerMedia/NCViewerMediaPage.swift | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 71b180398e..dc75f34e5d 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -87,25 +87,6 @@ class NCViewerMediaPage: UIViewController { viewerMediaScreenMode = .normal } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if currentViewController.metadata.isImage { - self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] - } else { - self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] - } - - self.tabBarController?.tabBar.isHidden = true - - view.setNeedsLayout() - view.layoutIfNeeded() - } - - override func viewWillDisappear(_ animated: Bool) { - self.tabBarController?.tabBar.isHidden = false - } - override func viewDidLoad() { super.viewDidLoad() @@ -172,11 +153,15 @@ class NCViewerMediaPage: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + guard let navigationController = self.navigationController else { return } + if currentViewController.metadata.isImage { self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] } else { self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] } + + self.tabBarController?.tabBar.isHidden = true } override func viewDidAppear(_ animated: Bool) { @@ -193,6 +178,12 @@ class NCViewerMediaPage: UIViewController { clearCommandCenter() } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + self.tabBarController?.tabBar.isHidden = false + } + override var preferredStatusBarStyle: UIStatusBarStyle { if viewerMediaScreenMode == .normal { From 01456666df0c5f96808ac2420b874bc231004556 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 16:29:34 +0200 Subject: [PATCH 035/103] Add sorting Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 13 ++++--------- iOSClient/Media/NCMediaViewModel.swift | 14 +++++++++++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 31bf0e5f58..d0ec58c313 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -35,10 +35,6 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate viewController.metadatas = metadatas NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) - -// let navController = UINavigationController(rootViewController: self) -// self.navigationController!.pushViewController(viewController, animated: true) -// self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) } } } @@ -77,7 +73,6 @@ struct NCMediaNew: View { @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false @State private var tappedMetadata = tableMetadata() - @State private var sort: Int = 0 @State private var isInSelectMode = false @State private var selectedMetadataInSelectMode: [tableMetadata] = [] @@ -179,10 +174,10 @@ struct NCMediaNew: View { }) } - Picker("Sorting options", selection: $sort) { - Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) - Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) - Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + Picker("Sorting options", selection: $vm.sortType) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) } .pickerStyle(.menu) } label: { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 33a5d0e2d2..702f24686e 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -9,6 +9,10 @@ import NextcloudKit import Combine +enum SortType: String { + case modifiedDate = "date", creationDate = "creationDate", uploadDate = "uploadDate" +} + @MainActor class NCMediaViewModel: ObservableObject { @Published var metadatas: [tableMetadata] = [] @@ -22,6 +26,8 @@ import Combine @Published internal var filterClassTypeImage = false @Published internal var filterClassTypeVideo = false + @Published internal var sortType: SortType = SortType(rawValue: CCUtility.getMediaSortDate()) ?? .modifiedDate + private var cancellables: Set = [] internal var needsLoadingMoreItems = true @@ -36,7 +42,13 @@ import Combine searchNewMedia() $filterClassTypeImage.sink { _ in self.loadData() }.store(in: &cancellables) - $filterClassTypeVideo.sink{ _ in self.loadData() }.store(in: &cancellables) + $filterClassTypeVideo.sink { _ in self.loadData() }.store(in: &cancellables) + $sortType.sink { sortType in + print(sortType.rawValue) + CCUtility.setMediaSortDate(sortType.rawValue) + self.loadData() + } + .store(in: &cancellables) } deinit { From 1ec093eb1b26bbd9940e734e5062e24a9bf2b304 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 13:30:27 +0200 Subject: [PATCH 036/103] Fix toolbar icons, add trash icon Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 16 +++ iOSClient/Extensions/Binding+Extension.swift | 16 +++ iOSClient/Media/Cell/NCMediaCell.swift | 17 +-- iOSClient/Media/NCMediaNew.swift | 131 +++++++++++-------- iOSClient/Media/NCMediaRow.swift | 4 +- 5 files changed, 118 insertions(+), 66 deletions(-) create mode 100644 iOSClient/Extensions/Binding+Extension.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 0f97c8a052..9f01ab842f 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -84,6 +84,13 @@ F30A6BF62AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A6BF72AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A6BF82AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BFA2ABAE84900148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFB2ABAE84900148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFC2ABAE84900148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFD2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -845,6 +852,7 @@ C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesIntegrationTests.swift; sourceTree = ""; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceKeys.swift; sourceTree = ""; }; + F30A6BF92ABAE84900148857 /* Binding+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Extension.swift"; sourceTree = ""; }; F30A96042A27299D00D7BCFE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = EnvVars.stencil; sourceTree = ""; }; F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EnvVars.generated.swift; path = Sourcery/EnvVars.generated.swift; sourceTree = ""; }; @@ -2149,6 +2157,7 @@ F77BB745289984CA0090FC19 /* UIViewController+Extension.swift */, F7E8A390295DC5E0006CB2D0 /* View+Extension.swift */, F7EE66AC2A20B226009AE765 /* UILabel+Extension.swift */, + F30A6BF92ABAE84900148857 /* Binding+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -3374,6 +3383,7 @@ F79B646326CA661600838ACA /* UIControl+Extension.swift in Sources */, F78A10C429322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */, F75CA1482962F13700B01130 /* HUDView.swift in Sources */, + F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */, AF4BF61C27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, AF817EF4274BC781009ED85B /* NCUserBaseUrl.swift in Sources */, F78E2D6B29AF02DB0024D4F3 /* Database.swift in Sources */, @@ -3457,6 +3467,7 @@ F7490E7529882BE2009DCE94 /* NCManageDatabase+Directory.swift in Sources */, F7864AD12A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */, F7490E8729882CA8009DCE94 /* ThreadSafeDictionary.swift in Sources */, + F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */, F757CC8729E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, F7490E8229882C80009DCE94 /* NCManageDatabase+E2EE.swift in Sources */, F7490E7829882C28009DCE94 /* NCUtility.swift in Sources */, @@ -3529,6 +3540,7 @@ AF22B217277D196700DAB0CC /* NCShareExtension+DataSource.swift in Sources */, F76D364728A4F8BF00214537 /* NCActivityIndicator.swift in Sources */, F749B654297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, + F30A6BFD2ABAE84A00148857 /* Binding+Extension.swift in Sources */, F780710A1EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m in Sources */, F79EC77F26316193004E59D6 /* NCRenameFile.swift in Sources */, AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */, @@ -3574,6 +3586,7 @@ F793E59D28B761E7005E4B02 /* NCNetworking.swift in Sources */, F7BF9D832934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */, F757CC8329E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, + F30A6BFB2ABAE84900148857 /* Binding+Extension.swift in Sources */, F74B6D962A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */, F749B652297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, F783030628B4C51E00B84583 /* String+Extension.swift in Sources */, @@ -3617,6 +3630,7 @@ F771E3D320E2392D00AFB62D /* FileProviderExtension.swift in Sources */, F771E3D520E2392D00AFB62D /* FileProviderItem.swift in Sources */, AF4BF616275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */, + F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */, F343A4B72A1E084300DDA874 /* PHAsset+Extension.swift in Sources */, F7434B3620E23FE000417916 /* NCManageDatabase.swift in Sources */, F798F0E725880609000DAFFD /* UIColor+Extension.swift in Sources */, @@ -3751,6 +3765,7 @@ F74B6D952A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */, F702F2F725EE5CED008F8E80 /* NCLogin.swift in Sources */, F7E98C1627E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */, + F30A6BFA2ABAE84900148857 /* Binding+Extension.swift in Sources */, F7F4F11227ECDC52008676F9 /* UIFont+Extension.swift in Sources */, AF93471A27E2361E002537EE /* NCShareAdvancePermissionHeader.swift in Sources */, F7F878AE1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */, @@ -3898,6 +3913,7 @@ F78E2D6729AF02DB0024D4F3 /* Database.swift in Sources */, F7A8D73628F17E1A008BBE1C /* NCManageDatabase+Activity.swift in Sources */, F7A8D73E28F181E2008BBE1C /* NCUserBaseUrl.swift in Sources */, + F30A6BFC2ABAE84900148857 /* Binding+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOSClient/Extensions/Binding+Extension.swift b/iOSClient/Extensions/Binding+Extension.swift new file mode 100644 index 0000000000..4fed3583fc --- /dev/null +++ b/iOSClient/Extensions/Binding+Extension.swift @@ -0,0 +1,16 @@ +// +// Binding+Extension.swift +// Nextcloud +// +// Created by Milen on 20.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI + +prefix func ! (value: Binding) -> Binding { + Binding( + get: { !value.wrappedValue }, + set: { value.wrappedValue = !$0 } + ) +} diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 4b118f59f9..cc195606a1 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -15,8 +15,8 @@ struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat @Binding var isInSelectMode: Bool - let onTap: (ScaledThumbnail, Bool) -> Void - @State private var isTappedInSelectMode = false + let onSelected: (ScaledThumbnail, Bool) -> Void + @State private var isSelected = false var body: some View { let image = Image(uiImage: thumbnail.image) @@ -38,7 +38,7 @@ struct NCMediaCell: View { } else { image } - } + }.disabled(isInSelectMode) } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -53,12 +53,12 @@ struct NCMediaCell: View { } } .overlay { - if isInSelectMode, isTappedInSelectMode { + if isInSelectMode, isSelected { Color.black.opacity(0.6).frame(maxWidth: .infinity) } } .overlay(alignment: .bottomTrailing) { - if isInSelectMode, isTappedInSelectMode { + if isInSelectMode, isSelected { Image(systemName: "checkmark.circle.fill") .resizable() .foregroundColor(.blue) @@ -72,11 +72,12 @@ struct NCMediaCell: View { .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) .background(Color(uiColor: .systemGray6)) .onTapGesture { - if isInSelectMode { isTappedInSelectMode.toggle() } - onTap(thumbnail, isTappedInSelectMode) + if isInSelectMode { isSelected.toggle() } + onSelected(thumbnail, isSelected) } .onChange(of: isInSelectMode) { newValue in - isTappedInSelectMode = !newValue + isSelected = !newValue + onSelected(thumbnail, isSelected) } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index d0ec58c313..5eddbc2ce2 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -76,29 +76,28 @@ struct NCMediaNew: View { @State private var isInSelectMode = false @State private var selectedMetadataInSelectMode: [tableMetadata] = [] + @State var titleColor = Color.primary + @State var toolbarItemsColor = Color.blue + @State var toolbarColors = [Color.clear] + weak var dataModelDelegate: DataDelegate? var body: some View { - NavigationView { GeometryReader { outerProxy in + NavigationView { ZStack(alignment: .top) { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, tappedInSelectMode in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, isSelected in - if tappedInSelectMode { + // TODO: Only do selection here + if isSelected { selectedMetadataInSelectMode.append(tappedThumbnail.metadata) + print(selectedMetadataInSelectMode) } else { selectedMetadataInSelectMode.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) - } - - print(selectedMetadataInSelectMode) - - if !isInSelectMode { - tappedMetadata = tappedThumbnail.metadata - dataModelDelegate?.updateData(metadatas: vm.metadatas, selectedMetadata: tappedMetadata, image: tappedThumbnail.image) -// isMediaViewControllerPresented = true + print(selectedMetadataInSelectMode) } } } @@ -117,8 +116,12 @@ struct NCMediaNew: View { .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) }) .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in - withAnimation(.easeInOut) { - isScrolledToTop = value.y >= -10 + let isScrolledToTop = value.y >= -10 + + withAnimation(.default) { + titleColor = isScrolledToTop ? Color.primary : .white + toolbarItemsColor = isScrolledToTop ? .blue : .white + toolbarColors = isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)] } } @@ -128,74 +131,74 @@ struct NCMediaNew: View { } .coordinateSpace(name: "scroll") + // Toolbar + HStack(content: { HStack { Text(title) .font(.system(size: 20, weight: .bold)) - .foregroundStyle(isScrolledToTop ? Color.primary : .white) + .foregroundStyle(titleColor) + Spacer() + Button(action: { isInSelectMode.toggle() }, label: { Text(NSLocalizedString(isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) - .foregroundStyle(isScrolledToTop ? .blue : .white) + .foregroundStyle(toolbarItemsColor) }) .padding(.horizontal, 6) .padding(.vertical, 3) .background(.ultraThinMaterial) .cornerRadius(.infinity) - if !isInSelectMode { - Menu { - Section { - Button(action: { - vm.filterClassTypeImage = !vm.filterClassTypeImage - vm.filterClassTypeVideo = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") - }) - Button(action: { - vm.filterClassTypeVideo = !vm.filterClassTypeVideo - vm.filterClassTypeImage = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") - }) - } + if isInSelectMode, !selectedMetadataInSelectMode.isEmpty { + ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) + } - Section { - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") - }) - } + Menu { + Section { + Button(action: { + vm.filterClassTypeImage = !vm.filterClassTypeImage + vm.filterClassTypeVideo = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") + }) + Button(action: { + vm.filterClassTypeVideo = !vm.filterClassTypeVideo + vm.filterClassTypeImage = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + }) + } - Picker("Sorting options", selection: $vm.sortType) { - Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) - Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) - Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) - } - .pickerStyle(.menu) - } label: { - Image(systemName: "ellipsis").font(.system(size: 15)) - .padding(.horizontal, 2) - .padding(.vertical, 8) - .background(.ultraThinMaterial) - .cornerRadius(.infinity) - .foregroundColor(isScrolledToTop ? Color.blue : .white) + Section { + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } + Picker("Sorting options", selection: $vm.sortType) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) } + .pickerStyle(.menu) + } label: { + ToolbarCircularButton(imageSystemName: "ellipsis", toolbarItemsColor: $toolbarItemsColor) } } }) .frame(maxWidth: .infinity) .padding([.horizontal, .top], 10) .padding(.bottom, 20) - .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + .background(LinearGradient(gradient: Gradient(colors: toolbarColors), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } .onRotate { orientation in @@ -221,6 +224,22 @@ struct NCMediaNew: View { } } +struct ToolbarCircularButton: View { + let imageSystemName: String + @Binding var toolbarItemsColor: Color + + var body: some View { + Image(systemName: imageSystemName) + .resizable() + .scaledToFit() + .frame(width: 13, height: 12) + .padding(5) + .background(.ultraThinMaterial) + .clipShape(Circle()) + .foregroundColor(toolbarItemsColor) + } +} + struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index c0add6f476..8b72a9e88f 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -14,7 +14,7 @@ struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy @Binding var isInSelectMode: Bool - let onCellTap: (ScaledThumbnail, Bool) -> Void + let onCellSelected: (ScaledThumbnail, Bool) -> Void @StateObject private var vm = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -26,7 +26,7 @@ struct NCMediaRow: View { } } else { ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onTap: onCellTap) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onSelected: onCellSelected) } } } From ab91eaba24f7b12e35256250cc6e25f90c0ffff6 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 28 Aug 2023 10:08:56 +0200 Subject: [PATCH 037/103] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 ++ iOSClient/Main/Main.storyboard | 20 ++++++--- iOSClient/Media/NCMediaNew.swift | 67 +++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 iOSClient/Media/NCMediaNew.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 9f6145a876..3b73140eda 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700510122DF63AC003A3356 /* NCShare.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F700510022DF63AC003A3356 /* NCShare.storyboard */; }; @@ -850,6 +851,7 @@ F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreUserCell.swift; sourceTree = ""; }; F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = ""; }; + F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaNew.swift; sourceTree = ""; }; F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = ""; }; F700510022DF63AC003A3356 /* NCShare.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShare.storyboard; sourceTree = ""; }; F700510222DF6897003A3356 /* Parchment.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Parchment.framework; path = Carthage/Build/iOS/Parchment.framework; sourceTree = ""; }; @@ -2377,6 +2379,7 @@ F7501C302212E57400FB1415 /* NCMedia.storyboard */, F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, + F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, ); path = Media; sourceTree = ""; @@ -3651,6 +3654,7 @@ F73B422C2476764F00A30FD3 /* NCNotification.swift in Sources */, 371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */, F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, + F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */, F79EDAA326B004980007D134 /* NCPlayerToolBar.swift in Sources */, F77444F8222816D5000D5EB0 /* NCPickerViewController.swift in Sources */, F77BB74A2899857B0090FC19 /* UINavigationController+Extension.swift in Sources */, diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index f6ed5e212b..3e3f022757 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -34,13 +34,13 @@ - + - + @@ -97,7 +97,7 @@ - + @@ -263,6 +263,16 @@ + + + + + + + + + + diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift new file mode 100644 index 0000000000..65be4e63b4 --- /dev/null +++ b/iOSClient/Media/NCMediaNew.swift @@ -0,0 +1,67 @@ +// +// NCMediaNew.swift +// Nextcloud +// +// Created by Milen on 25.08.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI +import PreviewSnapshots + +class NCMediaUIHostingController: UIHostingController { + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder, rootView: NCMediaNew()) + } +} + +struct NCMediaNew: View { + @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) + + var body: some View { + VStack { + ScrollView { + LazyVGrid(columns: gridColumns) { + ForEach(0...20, id: \.self) { value in + GeometryReader { geo in + ZStack(alignment: .topTrailing) { + // AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in + // image + // .resizable() + // .frame(width: CGFloat.random(in: 20...50), height: 50) + // } + AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in + image + .resizable() + .scaledToFill() + } placeholder: { + ProgressView() + } + .frame(width: CGFloat.random(in: 20...200), height: 50) + } + } + .cornerRadius(8.0) +// .aspectRatio(1, contentMode: .fit) + .aspectRatio(CGFloat.random(in: 1...5), contentMode: .fit) + } + } + } + } + } +} + +struct NCMediaNew_Previews: PreviewProvider { + static var previews: some View { + snapshots.previews.previewLayout(.sizeThatFits) + } + + static var snapshots: PreviewSnapshots { + PreviewSnapshots( + configurations: [ + .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") + ], + configure: { _ in + NCMediaNew() + }) + } +} From 6fcfd5bf538f270f1d23da82fd2c4b8e8190b601 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 28 Aug 2023 16:43:39 +0200 Subject: [PATCH 038/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 65be4e63b4..d998413bb7 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -21,15 +21,9 @@ struct NCMediaNew: View { var body: some View { VStack { ScrollView { - LazyVGrid(columns: gridColumns) { + LazyVGrid(columns: gridColumns, alignment: .leading) { ForEach(0...20, id: \.self) { value in GeometryReader { geo in - ZStack(alignment: .topTrailing) { - // AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in - // image - // .resizable() - // .frame(width: CGFloat.random(in: 20...50), height: 50) - // } AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in image .resizable() @@ -38,11 +32,8 @@ struct NCMediaNew: View { ProgressView() } .frame(width: CGFloat.random(in: 20...200), height: 50) - } } .cornerRadius(8.0) -// .aspectRatio(1, contentMode: .fit) - .aspectRatio(CGFloat.random(in: 1...5), contentMode: .fit) } } } From 4e6ba95626e9d24570ec1cec440d402c01c5c03d Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 28 Aug 2023 19:00:53 +0200 Subject: [PATCH 039/103] Make view model Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 17 ++++ iOSClient/Media/NCMediaNew.swift | 151 +++++++++++++++++++++++++--- 2 files changed, 155 insertions(+), 13 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 3b73140eda..01011ada23 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F3D4CC942A9CF2270015E283 /* ExyteGrid */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -1501,6 +1502,7 @@ F76DA969277B77EA0082465B /* DropDown in Frameworks */, F7F623B52A5EF4D30022D3D4 /* Gzip in Frameworks */, F75EAED826D2552E00F4320E /* MarqueeLabel in Frameworks */, + F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */, F710FC7A277B7D0000AA9FBF /* Realm in Frameworks */, F72DA9B425F53E4E00B87DB1 /* SwiftRichString in Frameworks */, F74E7720277A2EF40013B958 /* XLForm in Frameworks */, @@ -2892,6 +2894,7 @@ F7A1050D29E587AF00FFD92B /* TagListView */, F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, + F3D4CC942A9CF2270015E283 /* ExyteGrid */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3067,6 +3070,7 @@ F31F69622A2F929600162F76 /* XCRemoteSwiftPackageReference "swiftui-preview-snapshots" */, F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, + F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4913,6 +4917,14 @@ version = 1.4.0; }; }; + F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/exyte/Grid"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.5.0; + }; + }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; @@ -5205,6 +5217,11 @@ package = F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */; productName = SnapshotTestingHEIC; }; + F3D4CC942A9CF2270015E283 /* ExyteGrid */ = { + isa = XCSwiftPackageProductDependency; + package = F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */; + productName = ExyteGrid; + }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index d998413bb7..a64df9588b 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -8,6 +8,8 @@ import SwiftUI import PreviewSnapshots +import ExyteGrid +import NextcloudKit class NCMediaUIHostingController: UIHostingController { required init?(coder aDecoder: NSCoder) { @@ -16,29 +18,55 @@ class NCMediaUIHostingController: UIHostingController { } struct NCMediaNew: View { - @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) + @StateObject private var viewModel = NCMediaViewModel() + + @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50, maximum: .infinity)), count: 2) var body: some View { VStack { ScrollView { LazyVGrid(columns: gridColumns, alignment: .leading) { - ForEach(0...20, id: \.self) { value in - GeometryReader { geo in - AsyncImage(url: URL(string: "https://picsum.photos/id/237/536/354")) { image in - image - .resizable() - .scaledToFill() - } placeholder: { - ProgressView() - } - .frame(width: CGFloat.random(in: 20...200), height: 50) - } - .cornerRadius(8.0) + ForEach(viewModel.metadatas, id: \.self) { metadata in +// GeometryReader { geo in + MediaCellView(metadata: metadata) +// .frame(width: CGFloat.random(in: 20...200), height: 50) +// } +// .cornerRadius(8.0) } } } } } +// var body: some View { +// Grid(0.. Void) { + guard let appDelegate, !appDelegate.account.isEmpty else { return } + + if account != appDelegate.account { + self.metadatas = [] + account = appDelegate.account +// DispatchQueue.main.async { self.collectionView?.reloadData() } + } + +// DispatchQueue.global().async { + self.queryDB(isForced: true) +// DispatchQueue.main.sync { +// self.reloadDataThenPerform { +// self.updateMediaControlVisibility() +// self.mediaCommandTitle() +// completion(self.metadatas) +// } +// } +// } + } + + func queryDB(isForced: Bool = false) { + guard let appDelegate else { return } + + livePhoto = CCUtility.getLivePhoto() + + if let activeAccount = NCManageDatabase.shared.getActiveAccount() { + self.mediaPath = activeAccount.mediaPath + } + + let startServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath + + predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue) + + if filterClassTypeImage { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) + } else if filterClassTypeVideo { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) + } else { + predicate = predicateDefault + } + + guard let predicate = predicate else { return } + + metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) + + switch CCUtility.getMediaSortDate() { + case "date": + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)} ) + case "creationDate": + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)} ) + case "uploadDate": + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)} ) + default: + break + } + } +} From 663c7d435ef09fc1e13440581ff00674f103e154 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 31 Aug 2023 13:44:59 +0200 Subject: [PATCH 040/103] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 22 +-- iOSClient/Media/NCMediaNew.swift | 281 ++++++++++++++++++++++++---- 2 files changed, 253 insertions(+), 50 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 01011ada23..ffdeeded42 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -116,6 +116,7 @@ F343A4BF2A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C12A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; + F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F34624522AA08C5700FAA7B1 /* FlowGrid */; }; F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8682A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8692A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; @@ -130,7 +131,6 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; - F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F3D4CC942A9CF2270015E283 /* ExyteGrid */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -1494,6 +1494,7 @@ F76DA966277B76F30082465B /* UICKeyChainStore in Frameworks */, F7792DE529EEE02D005930CE /* MobileVLCKit.xcframework in Frameworks */, F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */, + F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */, F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */, F76DA963277B760E0082465B /* Queuer in Frameworks */, F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, @@ -1502,7 +1503,6 @@ F76DA969277B77EA0082465B /* DropDown in Frameworks */, F7F623B52A5EF4D30022D3D4 /* Gzip in Frameworks */, F75EAED826D2552E00F4320E /* MarqueeLabel in Frameworks */, - F3D4CC952A9CF2270015E283 /* ExyteGrid in Frameworks */, F710FC7A277B7D0000AA9FBF /* Realm in Frameworks */, F72DA9B425F53E4E00B87DB1 /* SwiftRichString in Frameworks */, F74E7720277A2EF40013B958 /* XLForm in Frameworks */, @@ -2894,7 +2894,7 @@ F7A1050D29E587AF00FFD92B /* TagListView */, F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, - F3D4CC942A9CF2270015E283 /* ExyteGrid */, + F34624522AA08C5700FAA7B1 /* FlowGrid */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3070,7 +3070,7 @@ F31F69622A2F929600162F76 /* XCRemoteSwiftPackageReference "swiftui-preview-snapshots" */, F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, - F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */, + F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4917,12 +4917,12 @@ version = 1.4.0; }; }; - F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */ = { + F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/exyte/Grid"; + repositoryURL = "https://github.com/rdev/FlowGrid.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.5.0; + kind = exactVersion; + version = 1.0.0; }; }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { @@ -5217,10 +5217,10 @@ package = F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */; productName = SnapshotTestingHEIC; }; - F3D4CC942A9CF2270015E283 /* ExyteGrid */ = { + F34624522AA08C5700FAA7B1 /* FlowGrid */ = { isa = XCSwiftPackageProductDependency; - package = F3D4CC932A9CF2270015E283 /* XCRemoteSwiftPackageReference "Grid" */; - productName = ExyteGrid; + package = F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */; + productName = FlowGrid; }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index a64df9588b..c1d57ea8cf 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -8,8 +8,8 @@ import SwiftUI import PreviewSnapshots -import ExyteGrid import NextcloudKit +import FlowGrid class NCMediaUIHostingController: UIHostingController { required init?(coder aDecoder: NSCoder) { @@ -17,26 +17,84 @@ class NCMediaUIHostingController: UIHostingController { } } +// extension Array { +// func chunked(into size: Int) -> [[Element]] { +// return stride(from: 0, to: count, by: size).map { +// Array(self[$0 ..< Swift.min($0 + size, count)]) +// } +// } +// } + +struct MinHeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = max(value, nextValue()) + } +} + struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() - @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50, maximum: .infinity)), count: 2) + @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) + + @State private var minHeight: CGFloat = 0 var body: some View { VStack { +// StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in +// MediaCellView(metadata: metadata) +// }) +// FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in +// MediaCellView(metadata: metadata) +// } ScrollView { - LazyVGrid(columns: gridColumns, alignment: .leading) { - ForEach(viewModel.metadatas, id: \.self) { metadata in -// GeometryReader { geo in - MediaCellView(metadata: metadata) -// .frame(width: CGFloat.random(in: 20...200), height: 50) +// HStack(alignment: .top) { +// LazyVStack(spacing: 8) { +// ForEach(viewModel.metadatas.chunked(into: viewModel.metadatas.count / 2), id: \.self) { rowMetadatas in +// ForEach(rowMetadatas, id: \.self) { metadata in +// MediaCellView(metadata: metadata) +// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// } +// } +// } +// +// LazyVStack(spacing: 8) { +// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in +// ForEach(rowMetadatas, id: \.self) { metadata in +// MediaCellView(metadata: metadata) +// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// } +// } +// } +// } +// LazyVGrid(columns: gridColumns, alignment: .leading) { +// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in +// ForEach(rowMetadatas, id: \.self) { metadata in +// MediaCellView(metadata: metadata) +//// .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// .frame(height: 300) // } -// .cornerRadius(8.0) +// } +// } + + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in + HStack(spacing: 0) { + ForEach(rowMetadatas, id: \.self) { metadata in + MediaCellView(shrinkRatio: viewModel.getSize(rowMetadatas: rowMetadatas), metadata: metadata) + // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) +// .frame(height: 300) + } + } + } } } } } +} + // var body: some View { // Grid(0.. { - PreviewSnapshots( - configurations: [ - .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") - ], - configure: { _ in - NCMediaNew() - }) + let wtf = CGFloat(metadata.width) * shrinkRatio + let _ = print(wtf) } } @MainActor class MediaCellViewModel: ObservableObject { - @Published var thumbnail: UIImage = UIImage() + @Published private(set) var thumbnail: UIImage = UIImage() + private var metadata: tableMetadata = tableMetadata() - func downloadThumbnail(metadata: tableMetadata) { + func configure(metadata: tableMetadata) { + self.metadata = metadata + } + + func downloadThumbnail() { let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { @@ -98,10 +149,49 @@ struct NCMediaNew_Previews: PreviewProvider { } } else { thumbnail = UIImage(systemName: "plus")! -// // Perform thumbnail download -// NCOperationQueue.shared.downloadThumbnail(metadata: metadata) { downloadedImage in -// self.thumbnail = downloadedImage -// } + + let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! + let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! + let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + + var etagResource: String? + if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { + etagResource = metadata.etagResource + } + let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.downloadPreview( + fileNamePathOrFileId: fileNamePath, + fileNamePreviewLocalPath: fileNamePreviewLocalPath, + widthPreview: Int(UIScreen.main.bounds.width), + heightPreview: Int(UIScreen.main.bounds.height) / 2, + fileNameIconLocalPath: fileNameIconLocalPath, + sizeIcon: NCGlobal.shared.sizeIcon, + etag: etagResource, + options: options) { _, _, imageIcon, _, etag, error in + + if error == .success, let imageIcon = imageIcon { + NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag) + DispatchQueue.main.async { +// if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { +// UIView.transition(with: filePreviewImageView, +// duration: 0.75, +// options: .transitionCrossDissolve, +// animations: { filePreviewImageView.image = imageIcon }, +// completion: nil) + self.thumbnail = imageIcon +// } else { +// if self.view is UICollectionView { +// (self.view as? UICollectionView)?.reloadData() +// } else if self.view is UITableView { +// (self.view as? UITableView)?.reloadData() +// } +// } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) + } + } +// self.finish() + } } } } @@ -171,13 +261,126 @@ struct NCMediaNew_Previews: PreviewProvider { switch CCUtility.getMediaSortDate() { case "date": - self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)} ) + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) case "creationDate": - self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)} ) + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) case "uploadDate": - self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)} ) + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) default: break } } + +// func getSmallestHeight(rowMetadatas: [tableMetadata], metadata: tableMetadata) -> CGFloat { +//// let scaleFactor = (rowMetadatas.compactMap { $0.height }.max() ?? 0) / metadata.height +// +// var newSummedWidth: CGFloat = 0 +// +// for metadata in rowMetadatas { +// let height1 = CGFloat(metadata.height == 0 ? 336 : metadata.height) +// let width1 = CGFloat(metadata.width == 0 ? 336 : metadata.width) +// +// let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 +// let newHeight1 = height1 * scaleFactor1 +// let newWidth1 = width1 * scaleFactor1 +// +// newSummedWidth += CGFloat(newWidth1) +// } +// +// return CGFloat(metadata.height * scaleFactor) +// } + + func getSize(rowMetadatas: [tableMetadata]) -> CGFloat { + let screenWidth = UIScreen.main.bounds.width + var newSummedWidth: CGFloat = 0 + + for metadata in rowMetadatas { + let height1 = CGFloat(metadata.height == 0 ? 500 : metadata.height) + let width1 = CGFloat(metadata.width == 0 ? 500 : metadata.width) + + let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 + let newHeight1 = height1 * scaleFactor1 + let newWidth1 = width1 * scaleFactor1 + + newSummedWidth += CGFloat(newWidth1) + } + + let shrinkRatio: CGFloat = screenWidth / newSummedWidth + + return shrinkRatio + } +} + +struct NCMediaNew_Previews: PreviewProvider { + static var previews: some View { + snapshots.previews.previewLayout(.sizeThatFits) + } + + static var snapshots: PreviewSnapshots { + PreviewSnapshots( + configurations: [ + .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") + ], + configure: { _ in + NCMediaNew() + }) + } +} + +extension Array { + func chunked(into size: Int) -> [[Element]] { + return stride(from: 0, to: count, by: size).map { + Array(self[$0..: View where T: Hashable { + + // MARK: - Properties + var content: (T) -> Content + var list: [T] + var columns: Int + var showIndicators: Bool + var spacing: CGFloat + + init(list: [T], columns: Int, showIndicators: Bool = false, spacing: CGFloat = 10, @ViewBuilder content: @escaping (T) -> Content) { + self.content = content + self.list = list + self.columns = columns + self.showIndicators = showIndicators + self.spacing = spacing + } + + func setUpList() -> [[T]] { + var gridArray: [[T]] = Array(repeating: [], count: columns) + var currentIndex: Int = 0 + for object in list { + gridArray[currentIndex].append(object) + if currentIndex == (columns - 1) { + currentIndex = 0 + } else { + currentIndex += 1 + } + } + return gridArray + } + + // MARK: - Body + var body: some View { + ScrollView(.vertical, showsIndicators: showIndicators) { + HStack(alignment: .top, spacing: 0) { + ForEach(setUpList(), id: \.self) { columnData in + LazyVStack(spacing: 0) { + ForEach(columnData, id: \.self) { object in + let _ = print(columnData) + content(object) + } + } + + } + } + .padding(.vertical) + } + } } From 00e29a9d974d754e742ea00d2193c016b2909336 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 4 Sep 2023 13:56:58 +0200 Subject: [PATCH 041/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 313 ++++++++++++++----------------- 1 file changed, 136 insertions(+), 177 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index c1d57ea8cf..869f3744eb 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -41,53 +41,18 @@ struct NCMediaNew: View { @State private var minHeight: CGFloat = 0 var body: some View { - VStack { -// StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in -// MediaCellView(metadata: metadata) -// }) -// FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in -// MediaCellView(metadata: metadata) -// } + // StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in + // MediaCellView(metadata: metadata) + // }) + // FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in + // MediaCellView(metadata: metadata) + // } + GeometryReader { proxy in ScrollView { -// HStack(alignment: .top) { -// LazyVStack(spacing: 8) { -// ForEach(viewModel.metadatas.chunked(into: viewModel.metadatas.count / 2), id: \.self) { rowMetadatas in -// ForEach(rowMetadatas, id: \.self) { metadata in -// MediaCellView(metadata: metadata) -// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// } -// } -// } -// -// LazyVStack(spacing: 8) { -// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in -// ForEach(rowMetadatas, id: \.self) { metadata in -// MediaCellView(metadata: metadata) -// // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// } -// } -// } -// } -// LazyVGrid(columns: gridColumns, alignment: .leading) { -// ForEach(viewModel.metadatas.chunked(into: gridColumns.count), id: \.self) { rowMetadatas in -// ForEach(rowMetadatas, id: \.self) { metadata in -// MediaCellView(metadata: metadata) -//// .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// .frame(height: 300) -// } -// } -// } - LazyVStack(alignment: .leading, spacing: 0) { ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in - HStack(spacing: 0) { - ForEach(rowMetadatas, id: \.self) { metadata in - MediaCellView(shrinkRatio: viewModel.getSize(rowMetadatas: rowMetadatas), metadata: metadata) - // .frame(width: CGFloat(metadata.width == 0 ? 500 : metadata.width * 2) * viewModel.getSize(rowMetadatas: rowMetadatas)) -// .frame(height: 300) - } - } + MediaRow(metadatas: rowMetadatas) } } } @@ -110,89 +75,153 @@ struct NCMediaNew: View { // } // } +struct MediaRow: View { + let metadatas: [tableMetadata] + @StateObject private var viewModel = MediaCellViewModel() + + var body: some View { + HStack(spacing: 0) { + if viewModel.thumbnails.isEmpty { + ProgressView() + } else { + ForEach(viewModel.thumbnails, id: \.self) { thumbnail in + let _ = print(viewModel.thumbnails) + MediaCellView(shrinkRatio: viewModel.shrinkRatio, thumbnail: thumbnail) + // get image here using async await and pass it to the MediaCellView + // MediaCellView(shrinkRatio: viewModel.getSize(), metadata: metadata) + } + } + } + .onAppear { + viewModel.configure(metadatas: metadatas) + viewModel.downloadThumbnails() + } + } +} + struct MediaCellView: View { let shrinkRatio: CGFloat - let metadata: tableMetadata - @StateObject private var viewModel = MediaCellViewModel() + let thumbnail: UIImage +// let metadata: tableMetadata +// @StateObject private var viewModel = MediaCellViewModel() var body: some View { - Image(uiImage: viewModel.thumbnail) + Image(uiImage: thumbnail) .resizable() -// .scaledToFit() - .frame(width: CGFloat(metadata.width) * shrinkRatio, height: CGFloat(metadata.height) * shrinkRatio) -// .scaledToFit() - .onAppear { - viewModel.configure(metadata: metadata) - viewModel.downloadThumbnail() - } +// .scaledToFill() + .frame(width: CGFloat(thumbnail.size.width * shrinkRatio), height: CGFloat(thumbnail.size.height * shrinkRatio)) - let wtf = CGFloat(metadata.width) * shrinkRatio - let _ = print(wtf) +// let wtf = CGFloat(metadata.width) * shrinkRatio +// let _ = print(viewModel.thumbnail.size.width) +// let _ = print(viewModel.thumbnail.size.height) } } @MainActor class MediaCellViewModel: ObservableObject { - @Published private(set) var thumbnail: UIImage = UIImage() - private var metadata: tableMetadata = tableMetadata() + @Published private(set) var thumbnails: [UIImage] = [] + private var metadatas: [tableMetadata] = [] + var shrinkRatio: CGFloat = 0 - func configure(metadata: tableMetadata) { - self.metadata = metadata + func configure(metadatas: [tableMetadata]) { + self.metadatas = metadatas } - func downloadThumbnail() { - let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) + func downloadThumbnails() { + var thumbnails: [UIImage] = [] - if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { - // Load thumbnail from file - if let image = UIImage(contentsOfFile: thumbnailPath) { - thumbnail = image - } - } else { - thumbnail = UIImage(systemName: "plus")! + metadatas.enumerated().forEach { index, metadata in + let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) - let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! - let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! - let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { + // Load thumbnail from file + if let image = UIImage(contentsOfFile: thumbnailPath) { + thumbnails.append(image) - var etagResource: String? - if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { - etagResource = metadata.etagResource - } - let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) - - NextcloudKit.shared.downloadPreview( - fileNamePathOrFileId: fileNamePath, - fileNamePreviewLocalPath: fileNamePreviewLocalPath, - widthPreview: Int(UIScreen.main.bounds.width), - heightPreview: Int(UIScreen.main.bounds.height) / 2, - fileNameIconLocalPath: fileNameIconLocalPath, - sizeIcon: NCGlobal.shared.sizeIcon, - etag: etagResource, - options: options) { _, _, imageIcon, _, etag, error in - - if error == .success, let imageIcon = imageIcon { - NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag) - DispatchQueue.main.async { -// if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { -// UIView.transition(with: filePreviewImageView, -// duration: 0.75, -// options: .transitionCrossDissolve, -// animations: { filePreviewImageView.image = imageIcon }, -// completion: nil) - self.thumbnail = imageIcon -// } else { -// if self.view is UICollectionView { -// (self.view as? UICollectionView)?.reloadData() -// } else if self.view is UITableView { -// (self.view as? UITableView)?.reloadData() -// } -// } - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) - } + if thumbnails.count == self.metadatas.count { + self.thumbnails = thumbnails + shrinkRatio = getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) } -// self.finish() } + } else { + let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! + let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! + let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + + var etagResource: String? + if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { + etagResource = metadata.etagResource + } + let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.downloadPreview( + fileNamePathOrFileId: fileNamePath, + fileNamePreviewLocalPath: fileNamePreviewLocalPath, + widthPreview: Int(UIScreen.main.bounds.width) / 2, + heightPreview: Int(UIScreen.main.bounds.height) / 2, + fileNameIconLocalPath: fileNameIconLocalPath, + sizeIcon: NCGlobal.shared.sizeIcon, + etag: etagResource, + options: options) { _, _, imageIcon, _, etag, error in + + if error == .success, let image = imageIcon { + NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) + DispatchQueue.main.async { + // if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { + // UIView.transition(with: filePreviewImageView, + // duration: 0.75, + // options: .transitionCrossDissolve, + // animations: { filePreviewImageView.image = imageIcon }, + // completion: nil) + thumbnails.append(image) + // } else { + // if self.view is UICollectionView { + // (self.view as? UICollectionView)?.reloadData() + // } else if self.view is UITableView { + // (self.view as? UITableView)?.reloadData() + // } + // } + +// self.metadata.height = Int(self.thumbnail.size.height) +// self.metadata.width = Int(self.thumbnail.size.width) +// print(self.thumbnail.size.width) +// print(self.thumbnail.size.height) +// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) + + print("------------") + print(thumbnails.count) + + if thumbnails.count == self.metadatas.count { + self.thumbnails = thumbnails + self.shrinkRatio = self.getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) + } + } + + + } + // self.finish() + } + } } + + } + + func getSize(rowMetadatas: [UIImage], fullWidth: CGFloat) -> CGFloat { + var newSummedWidth: CGFloat = 0 + let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 + for metadata in rowMetadatas { + let height1 = metadata.size.height + let width1 = metadata.size.width + + let scaleFactor1 = maxHeight / height1 +// let newHeight1 = height1 * scaleFactor1 + let newWidth1 = width1 * scaleFactor1 + + newSummedWidth += CGFloat(newWidth1) + } + + let shrinkRatio: CGFloat = fullWidth / newSummedWidth + + return shrinkRatio } } @@ -289,26 +318,6 @@ struct MediaCellView: View { // // return CGFloat(metadata.height * scaleFactor) // } - - func getSize(rowMetadatas: [tableMetadata]) -> CGFloat { - let screenWidth = UIScreen.main.bounds.width - var newSummedWidth: CGFloat = 0 - - for metadata in rowMetadatas { - let height1 = CGFloat(metadata.height == 0 ? 500 : metadata.height) - let width1 = CGFloat(metadata.width == 0 ? 500 : metadata.width) - - let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 - let newHeight1 = height1 * scaleFactor1 - let newWidth1 = width1 * scaleFactor1 - - newSummedWidth += CGFloat(newWidth1) - } - - let shrinkRatio: CGFloat = screenWidth / newSummedWidth - - return shrinkRatio - } } struct NCMediaNew_Previews: PreviewProvider { @@ -334,53 +343,3 @@ extension Array { } } } - -struct StaggeredGrid: View where T: Hashable { - - // MARK: - Properties - var content: (T) -> Content - var list: [T] - var columns: Int - var showIndicators: Bool - var spacing: CGFloat - - init(list: [T], columns: Int, showIndicators: Bool = false, spacing: CGFloat = 10, @ViewBuilder content: @escaping (T) -> Content) { - self.content = content - self.list = list - self.columns = columns - self.showIndicators = showIndicators - self.spacing = spacing - } - - func setUpList() -> [[T]] { - var gridArray: [[T]] = Array(repeating: [], count: columns) - var currentIndex: Int = 0 - for object in list { - gridArray[currentIndex].append(object) - if currentIndex == (columns - 1) { - currentIndex = 0 - } else { - currentIndex += 1 - } - } - return gridArray - } - - // MARK: - Body - var body: some View { - ScrollView(.vertical, showsIndicators: showIndicators) { - HStack(alignment: .top, spacing: 0) { - ForEach(setUpList(), id: \.self) { columnData in - LazyVStack(spacing: 0) { - ForEach(columnData, id: \.self) { object in - let _ = print(columnData) - content(object) - } - } - - } - } - .padding(.vertical) - } - } -} From fdb8d2effb7db08d7f0795d02ea348128dc18f56 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 10:39:04 +0200 Subject: [PATCH 042/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 153 +++++++++++++++++++------------ 1 file changed, 93 insertions(+), 60 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 869f3744eb..fd8edcb71a 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -36,20 +36,14 @@ struct MinHeightPreferenceKey: PreferenceKey { struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() - @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 2) +// @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 5) @State private var minHeight: CGFloat = 0 var body: some View { - // StaggeredGrid(list: viewModel.metadatas, columns: 2, content: { metadata in - // MediaCellView(metadata: metadata) - // }) - // FlowGrid(items: viewModel.metadatas, rowHeight: 200) { metadata in - // MediaCellView(metadata: metadata) - // } GeometryReader { proxy in ScrollView { - LazyVStack(alignment: .leading, spacing: 0) { + LazyVStack(alignment: .leading) { ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in MediaRow(metadatas: rowMetadatas) @@ -75,20 +69,32 @@ struct NCMediaNew: View { // } // } +struct RowData { + var scaledThumbnails: [ScaledThumbnail] = [] + var shrinkRatio: CGFloat = 0 +} + +struct ScaledThumbnail: Hashable { + let image: UIImage + var scaledSize: CGSize = .zero + let metadata: tableMetadata + + func hash(into hasher: inout Hasher) { + hasher.combine(image) + } +} + struct MediaRow: View { let metadatas: [tableMetadata] @StateObject private var viewModel = MediaCellViewModel() var body: some View { - HStack(spacing: 0) { - if viewModel.thumbnails.isEmpty { + HStack() { + if viewModel.rowData.scaledThumbnails.isEmpty { ProgressView() } else { - ForEach(viewModel.thumbnails, id: \.self) { thumbnail in - let _ = print(viewModel.thumbnails) - MediaCellView(shrinkRatio: viewModel.shrinkRatio, thumbnail: thumbnail) - // get image here using async await and pass it to the MediaCellView - // MediaCellView(shrinkRatio: viewModel.getSize(), metadata: metadata) + ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in + MediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) } } } @@ -100,16 +106,17 @@ struct MediaRow: View { } struct MediaCellView: View { + let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat - let thumbnail: UIImage +// let thumbnail: UIImage // let metadata: tableMetadata // @StateObject private var viewModel = MediaCellViewModel() var body: some View { - Image(uiImage: thumbnail) + Image(uiImage: thumbnail.image) .resizable() // .scaledToFill() - .frame(width: CGFloat(thumbnail.size.width * shrinkRatio), height: CGFloat(thumbnail.size.height * shrinkRatio)) + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) // let wtf = CGFloat(metadata.width) * shrinkRatio // let _ = print(viewModel.thumbnail.size.width) @@ -118,16 +125,18 @@ struct MediaCellView: View { } @MainActor class MediaCellViewModel: ObservableObject { - @Published private(set) var thumbnails: [UIImage] = [] +// @Published private(set) var thumbnails: [UIImage] = [] + @Published private(set) var rowData = RowData() + private var metadatas: [tableMetadata] = [] - var shrinkRatio: CGFloat = 0 +// var shrinkRatio: CGFloat = 0 func configure(metadatas: [tableMetadata]) { self.metadatas = metadatas } func downloadThumbnails() { - var thumbnails: [UIImage] = [] + var thumbnails: [ScaledThumbnail] = [] metadatas.enumerated().forEach { index, metadata in let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) @@ -135,11 +144,17 @@ struct MediaCellView: View { if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { // Load thumbnail from file if let image = UIImage(contentsOfFile: thumbnailPath) { - thumbnails.append(image) + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) if thumbnails.count == self.metadatas.count { - self.thumbnails = thumbnails - shrinkRatio = getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + + let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + + rowData.scaledThumbnails = thumbnails + rowData.shrinkRatio = shrinkRatio } } } else { @@ -168,61 +183,79 @@ struct MediaCellView: View { DispatchQueue.main.async { // if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { // UIView.transition(with: filePreviewImageView, - // duration: 0.75, - // options: .transitionCrossDissolve, - // animations: { filePreviewImageView.image = imageIcon }, - // completion: nil) - thumbnails.append(image) - // } else { - // if self.view is UICollectionView { - // (self.view as? UICollectionView)?.reloadData() - // } else if self.view is UITableView { - // (self.view as? UITableView)?.reloadData() - // } - // } - -// self.metadata.height = Int(self.thumbnail.size.height) -// self.metadata.width = Int(self.thumbnail.size.width) -// print(self.thumbnail.size.width) -// print(self.thumbnail.size.height) -// NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedThumbnail, userInfo: ["ocId": self.metadata.ocId]) - - print("------------") - print(thumbnails.count) + // duration: 0.75, + // options: .transitionCrossDissolve, + // animations: { filePreviewImageView.image = imageIcon }, + // completion: nil) + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) if thumbnails.count == self.metadatas.count { - self.thumbnails = thumbnails - self.shrinkRatio = self.getSize(rowMetadatas: thumbnails, fullWidth: UIScreen.main.bounds.width) - } - } + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: 1000) + self.rowData.scaledThumbnails = thumbnails + self.rowData.shrinkRatio = shrinkRatio + } + } } - // self.finish() } } } } - func getSize(rowMetadatas: [UIImage], fullWidth: CGFloat) -> CGFloat { - var newSummedWidth: CGFloat = 0 - let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 - for metadata in rowMetadatas { - let height1 = metadata.size.height - let width1 = metadata.size.width + func getScaledThumbnailSize(of thumbnail: ScaledThumbnail, thumbnailsInRow thumbnails: [ScaledThumbnail]) -> CGSize { + let maxHeight = thumbnails.compactMap { CGFloat($0.image.size.height) }.max() ?? 0 - let scaleFactor1 = maxHeight / height1 -// let newHeight1 = height1 * scaleFactor1 - let newWidth1 = width1 * scaleFactor1 + let height = thumbnail.image.size.height + let width = thumbnail.image.size.width + + let scaleFactor = maxHeight / height + let newHeight = height * scaleFactor + let newWidth = width * scaleFactor - newSummedWidth += CGFloat(newWidth1) +// return .init(image: thumbnail, scaledSize: .init(width: newWidth, height: newHeight)) + + + return .init(width: newWidth, height: newHeight) + } + + func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { + var newSummedWidth: CGFloat = 0 +// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 + for thumbnail in thumbnails { + newSummedWidth += CGFloat(thumbnail.scaledSize.width) } let shrinkRatio: CGFloat = fullWidth / newSummedWidth return shrinkRatio } + +// func getShrinkRatio(rowMetadatas: [UIImage], fullWidth: CGFloat) -> (height: CGFloat, shrinkRatio: CGFloat) { +// var newSummedWidth: CGFloat = 0 +// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 +// for metadata in rowMetadatas { +// let height1 = metadata.size.height +// let width1 = metadata.size.width +// +// let scaleFactor1 = maxHeight / height1 +// let newHeight1 = height1 * scaleFactor1 +// let newWidth1 = width1 * scaleFactor1 +// +//// scaledThumbnails.append(.init(image: metadata, scaledSize: .init(width: newWidth1, height: newHeight1))) +// +// newSummedWidth += CGFloat(newWidth1) +// } +// +// let shrinkRatio: CGFloat = fullWidth / newSummedWidth +// +// return shrinkRatio +// } + } @MainActor class NCMediaViewModel: ObservableObject { From 8e346db98b1735b8e178077d05cda2371fb58271 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 10:46:13 +0200 Subject: [PATCH 043/103] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 ++ iOSClient/Media/NCMediaNew.swift | 41 +----------------- iOSClient/Media/NCMediaRow.swift | 67 +++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 40 deletions(-) create mode 100644 iOSClient/Media/NCMediaRow.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index ffdeeded42..3f4e5c9ce8 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -127,6 +127,7 @@ F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */; }; F3953BD72A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */; }; F3A7AFC62A41AA82001FC89C /* BaseUIXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */; }; + F3AEDCA72AA720F800FDFA44 /* NCMediaRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */; }; F3BB464D2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3BB464C2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib */; }; F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; @@ -848,6 +849,7 @@ F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNCMoreCell.swift; sourceTree = ""; }; F3953BD62A6E87E000EE03F9 /* BaseIntegrationXCTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseIntegrationXCTestCase.swift; sourceTree = ""; }; F3A7AFC52A41AA82001FC89C /* BaseUIXCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseUIXCTestCase.swift; sourceTree = ""; }; + F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaRow.swift; sourceTree = ""; }; F3BB464C2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCMoreAppSuggestionsCell.xib; sourceTree = ""; }; F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreUserCell.swift; sourceTree = ""; }; F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; @@ -2382,6 +2384,7 @@ F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, + F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */, ); path = Media; sourceTree = ""; @@ -3731,6 +3734,7 @@ F7B6B70427C4E7FA00A7F6EB /* NCScan+CollectionView.swift in Sources */, F7C30DF6291BC0CA0017149B /* NCNetworkingE2EEUpload.swift in Sources */, F7501C332212E57500FB1415 /* NCMedia.swift in Sources */, + F3AEDCA72AA720F800FDFA44 /* NCMediaRow.swift in Sources */, F72944F22A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */, F70BFC7420E0FA7D00C67599 /* NCUtility.swift in Sources */, F79EDAA526B004980007D134 /* NCPlayer.swift in Sources */, diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index fd8edcb71a..0e1676365f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -17,14 +17,6 @@ class NCMediaUIHostingController: UIHostingController { } } -// extension Array { -// func chunked(into size: Int) -> [[Element]] { -// return stride(from: 0, to: count, by: size).map { -// Array(self[$0 ..< Swift.min($0 + size, count)]) -// } -// } -// } - struct MinHeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 @@ -181,12 +173,6 @@ struct MediaCellView: View { if error == .success, let image = imageIcon { NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) DispatchQueue.main.async { - // if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView { - // UIView.transition(with: filePreviewImageView, - // duration: 0.75, - // options: .transitionCrossDissolve, - // animations: { filePreviewImageView.image = imageIcon }, - // completion: nil) thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) if thumbnails.count == self.metadatas.count { @@ -217,15 +203,12 @@ struct MediaCellView: View { let newHeight = height * scaleFactor let newWidth = width * scaleFactor -// return .init(image: thumbnail, scaledSize: .init(width: newWidth, height: newHeight)) - - return .init(width: newWidth, height: newHeight) } func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { var newSummedWidth: CGFloat = 0 -// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 + for thumbnail in thumbnails { newSummedWidth += CGFloat(thumbnail.scaledSize.width) } @@ -234,28 +217,6 @@ struct MediaCellView: View { return shrinkRatio } - -// func getShrinkRatio(rowMetadatas: [UIImage], fullWidth: CGFloat) -> (height: CGFloat, shrinkRatio: CGFloat) { -// var newSummedWidth: CGFloat = 0 -// let maxHeight = rowMetadatas.compactMap { CGFloat($0.size.height) }.max() ?? 0 -// for metadata in rowMetadatas { -// let height1 = metadata.size.height -// let width1 = metadata.size.width -// -// let scaleFactor1 = maxHeight / height1 -// let newHeight1 = height1 * scaleFactor1 -// let newWidth1 = width1 * scaleFactor1 -// -//// scaledThumbnails.append(.init(image: metadata, scaledSize: .init(width: newWidth1, height: newHeight1))) -// -// newSummedWidth += CGFloat(newWidth1) -// } -// -// let shrinkRatio: CGFloat = fullWidth / newSummedWidth -// -// return shrinkRatio -// } - } @MainActor class NCMediaViewModel: ObservableObject { diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift new file mode 100644 index 0000000000..8901ccd6ff --- /dev/null +++ b/iOSClient/Media/NCMediaRow.swift @@ -0,0 +1,67 @@ +// +// NCMediaRow.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI +import PreviewSnapshots + +//struct NCMediaRow: View { +// let metadatas: [tableMetadata] +// @StateObject private var viewModel = MediaCellViewModel() +// +// var body: some View { +// HStack() { +// if viewModel.rowData.scaledThumbnails.isEmpty { +// ProgressView() +// } else { +// ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in +// MediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) +// } +// } +// } +// .onAppear { +// viewModel.configure(metadatas: metadatas) +// viewModel.downloadThumbnails() +// } +// } +//} +// +//struct MediaCellView: View { +// let thumbnail: ScaledThumbnail +// let shrinkRatio: CGFloat +//// let thumbnail: UIImage +//// let metadata: tableMetadata +//// @StateObject private var viewModel = MediaCellViewModel() +// +// var body: some View { +// Image(uiImage: thumbnail.image) +// .resizable() +//// .scaledToFill() +// .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) +// +//// let wtf = CGFloat(metadata.width) * shrinkRatio +//// let _ = print(viewModel.thumbnail.size.width) +//// let _ = print(viewModel.thumbnail.size.height) +// } +//} +// +//struct NCMediaRow_Previews: PreviewProvider { +// static var previews: some View { +// snapshots.previews.previewLayout(.sizeThatFits) +// } +// +// static var snapshots: PreviewSnapshots { +// PreviewSnapshots( +// configurations: [ +// .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") +// ], +// configure: { _ in +// NCMediaRow() +// }) +// } +//} + From e8b4230c6294b926f93d8f2efa18bbf0cdd18197 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 12:45:47 +0200 Subject: [PATCH 044/103] Separate components Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 12 + iOSClient/Extensions/Array+Extension.swift | 7 +- iOSClient/Media/Cell/NCMediaCellView.swift | 20 ++ iOSClient/Media/NCMediaNew.swift | 276 +-------------------- iOSClient/Media/NCMediaRow.swift | 75 ++---- iOSClient/Media/NCMediaRowViewModel.swift | 110 ++++++++ iOSClient/Media/NCMediaViewModel.swift | 75 ++++++ 7 files changed, 244 insertions(+), 331 deletions(-) create mode 100644 iOSClient/Media/Cell/NCMediaCellView.swift create mode 100644 iOSClient/Media/NCMediaRowViewModel.swift create mode 100644 iOSClient/Media/NCMediaViewModel.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 3f4e5c9ce8..003b2d5727 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -132,6 +132,9 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */; }; + F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */; }; + F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -854,6 +857,9 @@ F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreUserCell.swift; sourceTree = ""; }; F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = ""; }; + F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaRowViewModel.swift; sourceTree = ""; }; + F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCellView.swift; sourceTree = ""; }; + F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaViewModel.swift; sourceTree = ""; }; F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaNew.swift; sourceTree = ""; }; F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = ""; }; F700510022DF63AC003A3356 /* NCShare.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCShare.storyboard; sourceTree = ""; }; @@ -1752,6 +1758,7 @@ children = ( F77444F322281649000D5EB0 /* NCGridMediaCell.swift */, F77444F422281649000D5EB0 /* NCGridMediaCell.xib */, + F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */, ); path = Cell; sourceTree = ""; @@ -2384,7 +2391,9 @@ F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, + F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */, F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */, + F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */, ); path = Media; sourceTree = ""; @@ -3713,6 +3722,7 @@ AF817EF1274BC781009ED85B /* NCUserBaseUrl.swift in Sources */, F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */, AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */, + F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */, F7E41316294A19B300839300 /* UIView+Extension.swift in Sources */, F31F69502A2F707E00162F76 /* SwiftUIView+Extensions.swift in Sources */, F7C30E00291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */, @@ -3768,6 +3778,7 @@ F7B7504B2397D38F004E13EC /* UIImage+Extension.swift in Sources */, F7EFC0CD256BF8DD00461AAD /* NCUserStatus.swift in Sources */, AF3FDCC22796ECC300710F60 /* NCTrash+CollectionView.swift in Sources */, + F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */, F70D7C3725FFBF82002B9E34 /* NCCollectionViewCommon.swift in Sources */, F76D364628A4F8BF00214537 /* NCActivityIndicator.swift in Sources */, F7020FCE2233D7F700B7297D /* NCCreateFormUploadVoiceNote.swift in Sources */, @@ -3828,6 +3839,7 @@ F717402E24F699A5000C87D5 /* NCFavorite.swift in Sources */, AF2D7C7E2742559100ADF566 /* NCShareUserCell.swift in Sources */, AF7E505027A2D92300B5E4AF /* NCSelectableNavigationView.swift in Sources */, + F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */, F74DE14325135B6800917068 /* NCTransfers.swift in Sources */, AF4BF614275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */, AF4BF61E27562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */, diff --git a/iOSClient/Extensions/Array+Extension.swift b/iOSClient/Extensions/Array+Extension.swift index 0ff4eeae15..190f2f3975 100644 --- a/iOSClient/Extensions/Array+Extension.swift +++ b/iOSClient/Extensions/Array+Extension.swift @@ -26,7 +26,6 @@ import Foundation // https://stackoverflow.com/questions/33861036/unique-objects-inside-a-array-swift/45023247#45023247 extension Array { - func unique(map: ((Element) -> (T))) -> [Element] { var set = Set() // the unique list kept in a Set for fast retrieval var arrayOrdered = [Element]() // keeping the unique list of elements but ordered @@ -37,4 +36,10 @@ extension Array { return arrayOrdered } + + func chunked(into size: Int) -> [[Element]] { + return stride(from: 0, to: count, by: size).map { + Array(self[$0.. { } } -struct MinHeightPreferenceKey: PreferenceKey { - static var defaultValue: CGFloat = 0 - - static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { - value = max(value, nextValue()) - } -} - struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() -// @State private var gridColumns = Array(repeating: GridItem(.flexible(minimum: 50)), count: 5) - - @State private var minHeight: CGFloat = 0 - var body: some View { GeometryReader { proxy in ScrollView { LazyVStack(alignment: .leading) { ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in - - MediaRow(metadatas: rowMetadatas) + NCMediaRow(metadatas: rowMetadatas) } } } @@ -46,21 +33,6 @@ struct NCMediaNew: View { } } -// var body: some View { -// Grid(0.. CGSize { - let maxHeight = thumbnails.compactMap { CGFloat($0.image.size.height) }.max() ?? 0 - - let height = thumbnail.image.size.height - let width = thumbnail.image.size.width - - let scaleFactor = maxHeight / height - let newHeight = height * scaleFactor - let newWidth = width * scaleFactor - - return .init(width: newWidth, height: newHeight) - } - - func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { - var newSummedWidth: CGFloat = 0 - - for thumbnail in thumbnails { - newSummedWidth += CGFloat(thumbnail.scaledSize.width) - } - - let shrinkRatio: CGFloat = fullWidth / newSummedWidth - - return shrinkRatio - } -} - -@MainActor class NCMediaViewModel: ObservableObject { - @Published var metadatas: [tableMetadata] = [] - - private var account: String = "" - private var lastContentOffsetY: CGFloat = 0 - private var mediaPath = "" - private var livePhoto: Bool = false - private var predicateDefault: NSPredicate? - private var predicate: NSPredicate? - private let appDelegate = UIApplication.shared.delegate as? AppDelegate - internal var filterClassTypeImage = false - internal var filterClassTypeVideo = false - - init() { - reloadDataSourceWithCompletion { _ in } - } - - @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { - guard let appDelegate, !appDelegate.account.isEmpty else { return } - - if account != appDelegate.account { - self.metadatas = [] - account = appDelegate.account -// DispatchQueue.main.async { self.collectionView?.reloadData() } - } - -// DispatchQueue.global().async { - self.queryDB(isForced: true) -// DispatchQueue.main.sync { -// self.reloadDataThenPerform { -// self.updateMediaControlVisibility() -// self.mediaCommandTitle() -// completion(self.metadatas) -// } -// } -// } - } - - func queryDB(isForced: Bool = false) { - guard let appDelegate else { return } - - livePhoto = CCUtility.getLivePhoto() - - if let activeAccount = NCManageDatabase.shared.getActiveAccount() { - self.mediaPath = activeAccount.mediaPath - } - - let startServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath - - predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue) - - if filterClassTypeImage { - predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) - } else if filterClassTypeVideo { - predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) - } else { - predicate = predicateDefault - } - - guard let predicate = predicate else { return } - - metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) - - switch CCUtility.getMediaSortDate() { - case "date": - self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) - case "creationDate": - self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) - case "uploadDate": - self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) - default: - break - } - } - -// func getSmallestHeight(rowMetadatas: [tableMetadata], metadata: tableMetadata) -> CGFloat { -//// let scaleFactor = (rowMetadatas.compactMap { $0.height }.max() ?? 0) / metadata.height -// -// var newSummedWidth: CGFloat = 0 -// -// for metadata in rowMetadatas { -// let height1 = CGFloat(metadata.height == 0 ? 336 : metadata.height) -// let width1 = CGFloat(metadata.width == 0 ? 336 : metadata.width) -// -// let scaleFactor1 = (rowMetadatas.compactMap { CGFloat($0.height) }.max() ?? 0) / height1 -// let newHeight1 = height1 * scaleFactor1 -// let newWidth1 = width1 * scaleFactor1 -// -// newSummedWidth += CGFloat(newWidth1) -// } -// -// return CGFloat(metadata.height * scaleFactor) -// } -} - struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) @@ -329,11 +63,3 @@ struct NCMediaNew_Previews: PreviewProvider { }) } } - -extension Array { - func chunked(into size: Int) -> [[Element]] { - return stride(from: 0, to: count, by: size).map { - Array(self[$0.. { -// PreviewSnapshots( -// configurations: [ -// .init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "") -// ], -// configure: { _ in -// NCMediaRow() -// }) -// } -//} +struct NCMediaRow: View { + let metadatas: [tableMetadata] + @StateObject private var viewModel = NCMediaRowViewModel() + + var body: some View { + HStack() { + if viewModel.rowData.scaledThumbnails.isEmpty { + ProgressView() + } else { + ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in + NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) + } + } + } + .onAppear { + viewModel.configure(metadatas: metadatas) + viewModel.downloadThumbnails() + } + } +} diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift new file mode 100644 index 0000000000..debefaa909 --- /dev/null +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -0,0 +1,110 @@ +// +// NCMediaRowViewModel.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import NextcloudKit + +@MainActor class NCMediaRowViewModel: ObservableObject { + @Published private(set) var rowData = RowData() + + private var metadatas: [tableMetadata] = [] + + func configure(metadatas: [tableMetadata]) { + self.metadatas = metadatas + } + + func downloadThumbnails() { + var thumbnails: [ScaledThumbnail] = [] + + metadatas.enumerated().forEach { index, metadata in + let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) + + if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { + // Load thumbnail from file + if let image = UIImage(contentsOfFile: thumbnailPath) { + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + + if thumbnails.count == self.metadatas.count { + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + + let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + + rowData.scaledThumbnails = thumbnails + rowData.shrinkRatio = shrinkRatio + } + } + } else { + let fileNamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)! + let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)! + let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)! + + var etagResource: String? + if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) { + etagResource = metadata.etagResource + } + let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.downloadPreview( + fileNamePathOrFileId: fileNamePath, + fileNamePreviewLocalPath: fileNamePreviewLocalPath, + widthPreview: Int(UIScreen.main.bounds.width) / 2, + heightPreview: Int(UIScreen.main.bounds.height) / 2, + fileNameIconLocalPath: fileNameIconLocalPath, + sizeIcon: NCGlobal.shared.sizeIcon, + etag: etagResource, + options: options) { _, _, imageIcon, _, etag, error in + if error == .success, let image = imageIcon { + NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) + DispatchQueue.main.async { + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + + if thumbnails.count == self.metadatas.count { + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } + + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + + self.rowData.scaledThumbnails = thumbnails + self.rowData.shrinkRatio = shrinkRatio + } + } + } + } + } + } + + } + + func getScaledThumbnailSize(of thumbnail: ScaledThumbnail, thumbnailsInRow thumbnails: [ScaledThumbnail]) -> CGSize { + let maxHeight = thumbnails.compactMap { CGFloat($0.image.size.height) }.max() ?? 0 + + let height = thumbnail.image.size.height + let width = thumbnail.image.size.width + + let scaleFactor = maxHeight / height + let newHeight = height * scaleFactor + let newWidth = width * scaleFactor + + return .init(width: newWidth, height: newHeight) + } + + func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { + var newSummedWidth: CGFloat = 0 + + for thumbnail in thumbnails { + newSummedWidth += CGFloat(thumbnail.scaledSize.width) + } + + let shrinkRatio: CGFloat = fullWidth / newSummedWidth + + return shrinkRatio + } +} diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift new file mode 100644 index 0000000000..edd01a03d0 --- /dev/null +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -0,0 +1,75 @@ +// +// NCMediaViewModel.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import NextcloudKit + +@MainActor class NCMediaViewModel: ObservableObject { + @Published var metadatas: [tableMetadata] = [] + + private var account: String = "" + private var lastContentOffsetY: CGFloat = 0 + private var mediaPath = "" + private var livePhoto: Bool = false + private var predicateDefault: NSPredicate? + private var predicate: NSPredicate? + private let appDelegate = UIApplication.shared.delegate as? AppDelegate + internal var filterClassTypeImage = false + internal var filterClassTypeVideo = false + + init() { + reloadDataSourceWithCompletion { _ in } + } + + @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { + guard let appDelegate, !appDelegate.account.isEmpty else { return } + + if account != appDelegate.account { + self.metadatas = [] + account = appDelegate.account + } + + self.queryDB(isForced: true) + } + + func queryDB(isForced: Bool = false) { + guard let appDelegate else { return } + + livePhoto = CCUtility.getLivePhoto() + + if let activeAccount = NCManageDatabase.shared.getActiveAccount() { + self.mediaPath = activeAccount.mediaPath + } + + let startServerUrl = NCUtilityFileSystem.shared.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath + + predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue) + + if filterClassTypeImage { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) + } else if filterClassTypeVideo { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) + } else { + predicate = predicateDefault + } + + guard let predicate = predicate else { return } + + metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) + + switch CCUtility.getMediaSortDate() { + case "date": + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) + case "creationDate": + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) + case "uploadDate": + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) + default: + break + } + } +} From a31932b2791005c9384ab766ad654dc1393d0955 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 15:02:04 +0200 Subject: [PATCH 045/103] WIP Signed-off-by: Milen Pivchev --- .../Extensions/SwiftUIView+Extensions.swift | 17 +++++++++++ iOSClient/Media/NCMediaNew.swift | 28 +++++++------------ iOSClient/Media/NCMediaRow.swift | 7 +++-- iOSClient/Media/NCMediaRowViewModel.swift | 26 +++++++++++++---- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift index 3e7fe20d7d..303daa231c 100644 --- a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift +++ b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift @@ -9,10 +9,27 @@ import Foundation import SwiftUI +// Custom view modifier to track rotation and call an action +struct DeviceOrientationViewModifier: ViewModifier { + let action: (UIDeviceOrientation) -> Void + + func body(content: Content) -> some View { + content + .onAppear() + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + action(UIDevice.current.orientation) + } + } +} + extension SwiftUI.View { func toVC() -> UIViewController { let vc = UIHostingController (rootView: self) vc.view.frame = UIScreen.main.bounds return vc } + + func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View { + self.modifier(DeviceOrientationViewModifier(action: action)) + } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 2efec4843f..ce9986cdf9 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -19,35 +19,27 @@ class NCMediaUIHostingController: UIHostingController { struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() + @State var columns = 2 var body: some View { GeometryReader { proxy in ScrollView { - LazyVStack(alignment: .leading) { - ForEach(viewModel.metadatas.chunked(into: 2), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas) + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: proxy) } } + }.onRotate { orientation in + if orientation.isLandscapeHardCheck { + columns = 6 + } else { + columns = 2 + } } } } } -struct RowData { - var scaledThumbnails: [ScaledThumbnail] = [] - var shrinkRatio: CGFloat = 0 -} - -struct ScaledThumbnail: Hashable { - let image: UIImage - var scaledSize: CGSize = .zero - let metadata: tableMetadata - - func hash(into hasher: inout Hasher) { - hasher.combine(image) - } -} - struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index ea730a987b..39aa738512 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -11,10 +11,13 @@ import PreviewSnapshots struct NCMediaRow: View { let metadatas: [tableMetadata] + let geometryProxy: GeometryProxy + @StateObject private var viewModel = NCMediaRowViewModel() + private let spacing: CGFloat = 2 var body: some View { - HStack() { + HStack(spacing: spacing) { if viewModel.rowData.scaledThumbnails.isEmpty { ProgressView() } else { @@ -25,7 +28,7 @@ struct NCMediaRow: View { } .onAppear { viewModel.configure(metadatas: metadatas) - viewModel.downloadThumbnails() + viewModel.downloadThumbnails(rowWidth: geometryProxy.size.width, spacing: spacing) } } } diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift index debefaa909..e507735c50 100644 --- a/iOSClient/Media/NCMediaRowViewModel.swift +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -9,6 +9,21 @@ import Foundation import NextcloudKit +struct RowData { + var scaledThumbnails: [ScaledThumbnail] = [] + var shrinkRatio: CGFloat = 0 +} + +struct ScaledThumbnail: Hashable { + let image: UIImage + var scaledSize: CGSize = .zero + let metadata: tableMetadata + + func hash(into hasher: inout Hasher) { + hasher.combine(image) + } +} + @MainActor class NCMediaRowViewModel: ObservableObject { @Published private(set) var rowData = RowData() @@ -18,7 +33,7 @@ import NextcloudKit self.metadatas = metadatas } - func downloadThumbnails() { + func downloadThumbnails(rowWidth: CGFloat, spacing: CGFloat) { var thumbnails: [ScaledThumbnail] = [] metadatas.enumerated().forEach { index, metadata in @@ -34,7 +49,7 @@ import NextcloudKit thumbnails[index].scaledSize = getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) } - let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + let shrinkRatio = getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) rowData.scaledThumbnails = thumbnails rowData.shrinkRatio = shrinkRatio @@ -70,7 +85,7 @@ import NextcloudKit thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) } - let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: UIScreen.main.bounds.width) + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) self.rowData.scaledThumbnails = thumbnails self.rowData.shrinkRatio = shrinkRatio @@ -96,14 +111,15 @@ import NextcloudKit return .init(width: newWidth, height: newHeight) } - func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat) -> CGFloat { + func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat, spacing: CGFloat) -> CGFloat { var newSummedWidth: CGFloat = 0 for thumbnail in thumbnails { newSummedWidth += CGFloat(thumbnail.scaledSize.width) } - let shrinkRatio: CGFloat = fullWidth / newSummedWidth + let spacingWidth = spacing * CGFloat(thumbnails.count - 1) + let shrinkRatio: CGFloat = (fullWidth - spacingWidth) / newSummedWidth return shrinkRatio } From 083a4b0393ce27ec89ac53bb8ccef821fdb4621e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 5 Sep 2023 15:52:57 +0200 Subject: [PATCH 046/103] Add top header Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index ce9986cdf9..2c18a0bc32 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -22,14 +22,25 @@ struct NCMediaNew: View { @State var columns = 2 var body: some View { - GeometryReader { proxy in - ScrollView { - LazyVStack(alignment: .leading, spacing: 2) { - ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: proxy) + GeometryReader { geometry in + ZStack(alignment: .top) { + ScrollView { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: geometry) + } } } - }.onRotate { orientation in + + HStack(content: { + Text("Placeholder") + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(.white) + }) + .frame(maxWidth: .infinity) + .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + } + .onRotate { orientation in if orientation.isLandscapeHardCheck { columns = 6 } else { From 506bd46747a683a39912efca4eca21da031f5581 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 11 Sep 2023 14:37:43 +0200 Subject: [PATCH 047/103] Add notifications Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaViewModel.swift | 82 +++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index edd01a03d0..c74dcece2b 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -23,6 +23,20 @@ import NextcloudKit init() { reloadDataSourceWithCompletion { _ in } + + NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(moveFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) } @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { @@ -33,7 +47,7 @@ import NextcloudKit account = appDelegate.account } - self.queryDB(isForced: true) + self.queryDB(isForced: true) } func queryDB(isForced: Bool = false) { @@ -73,3 +87,69 @@ import NextcloudKit } } } + +// MARK: Notifications + +extension NCMediaViewModel { + @objc func deleteFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError else { return } + let onlyLocalCache: Bool = userInfo["onlyLocalCache"] as? Bool ?? false + + self.queryDB(isForced: true) + + if error == .success, let indexPath = userInfo["indexPath"] as? [IndexPath], !indexPath.isEmpty, !onlyLocalCache { + // collectionView?.performBatchUpdates({ + // collectionView?.deleteItems(at: indexPath) + // }, completion: { _ in + // self.collectionView?.reloadData() + // }) + } else { + if error != .success { + NCContentPresenter.shared.showError(error: error) + } + // self.collectionView?.reloadData() + } + + // if let hud = userInfo["hud"] as? JGProgressHUD { + // hud.dismiss() + // } + } + + @objc func moveFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary? else { return } + + // if let hud = userInfo["hud"] as? JGProgressHUD { + // hud.dismiss() + // } + } + + @objc func copyFile(_ notification: NSNotification) { + + moveFile(notification) + } + + @objc func renameFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary?, + let account = userInfo["account"] as? String, + account == appDelegate?.account + else { return } + + self.reloadDataSourceWithCompletion { _ in } + } + + @objc func uploadedFile(_ notification: NSNotification) { + + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError, + error == .success, + let account = userInfo["account"] as? String, + account == appDelegate?.account + else { return } + + self.reloadDataSourceWithCompletion { _ in } + } +} From 98cffdbcb80b64a2c3e486b6cf651f73d22fed76 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 11 Sep 2023 18:33:31 +0200 Subject: [PATCH 048/103] Try to get first cell in list Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCellView.swift | 30 +++++++++++++++++++--- iOSClient/Media/NCMediaNew.swift | 18 ++++++++++--- iOSClient/Media/NCMediaRow.swift | 5 ++-- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCellView.swift b/iOSClient/Media/Cell/NCMediaCellView.swift index 94a14f5a2c..98793a4bd8 100644 --- a/iOSClient/Media/Cell/NCMediaCellView.swift +++ b/iOSClient/Media/Cell/NCMediaCellView.swift @@ -11,10 +11,34 @@ import SwiftUI struct NCMediaCellView: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat + let outerProxy: GeometryProxy + @Binding var title: String var body: some View { - Image(uiImage: thumbnail.image) - .resizable() - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + GeometryReader { geometry in + Image(uiImage: thumbnail.image) + .resizable() + // .scaledToFit() + // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in + if isInView(innerRect: rect, isIn: outerProxy) { + print(thumbnail.metadata.fileName) + title = thumbnail.metadata.fileName + } + } + } + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + } + + private func isInView(innerRect:CGRect, isIn outerProxy:GeometryProxy) -> Bool { + let innerOrigin = innerRect.origin.x + let imageWidth = innerRect.width + let scrollOrigin = outerProxy.frame(in: .global).origin.x + let scrollWidth = outerProxy.size.width + if innerOrigin + imageWidth < scrollOrigin + scrollWidth && innerOrigin + imageWidth > scrollOrigin || + innerOrigin + imageWidth > scrollOrigin && innerOrigin < scrollOrigin + scrollWidth { + return true + } + return false } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 2c18a0bc32..271688b907 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -20,20 +20,28 @@ class NCMediaUIHostingController: UIHostingController { struct NCMediaNew: View { @StateObject private var viewModel = NCMediaViewModel() @State var columns = 2 + @State var title = "" + + public static let scrollViewCoordinateSpace = "scrollView" var body: some View { - GeometryReader { geometry in + GeometryReader { outerProxy in ZStack(alignment: .top) { ScrollView { LazyVStack(alignment: .leading, spacing: 2) { ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: geometry) + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) +// .onChange(of: geometry.frame(in: .local)) { rect in +// if isInView(innerRect: rect, isIn: outerProxy) { +// print("WOW") +// } +// } } } - } + }.coordinateSpace(name: NCMediaNew.scrollViewCoordinateSpace) HStack(content: { - Text("Placeholder") + Text(title) .font(.system(size: 20, weight: .bold)) .foregroundStyle(.white) }) @@ -49,8 +57,10 @@ struct NCMediaNew: View { } } } + } + struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 39aa738512..232528e865 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -12,7 +12,8 @@ import PreviewSnapshots struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy - + @Binding var title: String + @StateObject private var viewModel = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -22,7 +23,7 @@ struct NCMediaRow: View { ProgressView() } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) + NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) } } } From 3667c95f71883150c52dda74f3024d048b210492 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 13:41:33 +0200 Subject: [PATCH 049/103] Add date update Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 25 ++++++++-- iOSClient/Media/Cell/NCMediaCell.swift | 57 ++++++++++++++++++++++ iOSClient/Media/Cell/NCMediaCellView.swift | 44 ----------------- iOSClient/Media/NCMediaNew.swift | 25 ++++++---- iOSClient/Media/NCMediaRow.swift | 3 +- 5 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 iOSClient/Media/Cell/NCMediaCell.swift delete mode 100644 iOSClient/Media/Cell/NCMediaCellView.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 003b2d5727..1dc8666097 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -132,8 +132,9 @@ F3BB464F2A39EBE500461F6E /* NCMoreUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB464E2A39EBE500461F6E /* NCMoreUserCell.swift */; }; F3BB46522A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */; }; F3BB46542A3A1E9D00461F6E /* CCCellMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */; }; + F3C0FB702AB06AB9007E7E30 /* VisibilityTrackingScrollView in Frameworks */ = {isa = PBXBuildFile; productRef = F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */; }; F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */; }; - F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */; }; + F3E3B3BB2AA7396F006D08F5 /* NCMediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */; }; F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -858,7 +859,7 @@ F3BB46512A39EC4900461F6E /* NCMoreAppSuggestionsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMoreAppSuggestionsCell.swift; sourceTree = ""; }; F3BB46532A3A1E9D00461F6E /* CCCellMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CCCellMore.swift; sourceTree = ""; }; F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaRowViewModel.swift; sourceTree = ""; }; - F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCellView.swift; sourceTree = ""; }; + F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCell.swift; sourceTree = ""; }; F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaViewModel.swift; sourceTree = ""; }; F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaNew.swift; sourceTree = ""; }; F700222B1EC479840080073F /* Custom.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Custom.xcassets; sourceTree = ""; }; @@ -1495,6 +1496,7 @@ F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */, F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */, F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */, + F3C0FB702AB06AB9007E7E30 /* VisibilityTrackingScrollView in Frameworks */, F770768E263A8C3400A1BA94 /* FloatingPanel in Frameworks */, F710FC7C277B7D0000AA9FBF /* RealmSwift in Frameworks */, F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */, @@ -1758,7 +1760,7 @@ children = ( F77444F322281649000D5EB0 /* NCGridMediaCell.swift */, F77444F422281649000D5EB0 /* NCGridMediaCell.xib */, - F3E3B3BA2AA7396F006D08F5 /* NCMediaCellView.swift */, + F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */, ); path = Cell; sourceTree = ""; @@ -2907,6 +2909,7 @@ F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, F34624522AA08C5700FAA7B1 /* FlowGrid */, + F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3083,6 +3086,7 @@ F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */, + F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -3722,7 +3726,7 @@ AF817EF1274BC781009ED85B /* NCUserBaseUrl.swift in Sources */, F39298972A3B12CB00509762 /* BaseNCMoreCell.swift in Sources */, AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */, - F3E3B3BB2AA7396F006D08F5 /* NCMediaCellView.swift in Sources */, + F3E3B3BB2AA7396F006D08F5 /* NCMediaCell.swift in Sources */, F7E41316294A19B300839300 /* UIView+Extension.swift in Sources */, F31F69502A2F707E00162F76 /* SwiftUIView+Extensions.swift in Sources */, F7C30E00291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */, @@ -4941,6 +4945,14 @@ version = 1.0.0; }; }; + F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/elegantchaos/VisibilityTrackingScrollView"; + requirement = { + kind = exactVersion; + version = 1.0.3; + }; + }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; @@ -5238,6 +5250,11 @@ package = F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */; productName = FlowGrid; }; + F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */ = { + isa = XCSwiftPackageProductDependency; + package = F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */; + productName = VisibilityTrackingScrollView; + }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift new file mode 100644 index 0000000000..9005b3b39e --- /dev/null +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -0,0 +1,57 @@ +// +// NCMediaCellView.swift +// Nextcloud +// +// Created by Milen on 05.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI +import VisibilityTrackingScrollView + +struct NCMediaCell: View { + let thumbnail: ScaledThumbnail + let shrinkRatio: CGFloat + let outerProxy: GeometryProxy + @Binding var title: String +// @State private var visibleIndex: Set = [0,1] + @State private var visibleMetadata: IsCellVisiblePreferenceKey.PreferenceValue? = nil + + + var body: some View { + ZStack { + GeometryReader { geometry in + Image(uiImage: thumbnail.image) + .resizable() + // .scaledToFit() + // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) +// .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in +// if isInView(innerRect: rect, isIn: outerProxy) { +// visibleMetadata = .init(isVisible: true, metadata: thumbnail.metadata) +// } else { +// visibleMetadata = .init(isVisible: false, metadata: thumbnail.metadata) +// } +// } +// .preference(key: IsCellVisiblePreferenceKey.self, value: visibleMetadata) + } + .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + + Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) + } + } +} + +struct IsCellVisiblePreferenceKey: PreferenceKey { + struct PreferenceValue: Equatable { + let isVisible: Bool + let metadata: tableMetadata + } + + typealias Value = PreferenceValue? + static var defaultValue: Value = nil + + static func reduce(value: inout Value, nextValue: () -> Value) { + value = value ?? nextValue() + } +} diff --git a/iOSClient/Media/Cell/NCMediaCellView.swift b/iOSClient/Media/Cell/NCMediaCellView.swift deleted file mode 100644 index 98793a4bd8..0000000000 --- a/iOSClient/Media/Cell/NCMediaCellView.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// NCMediaCellView.swift -// Nextcloud -// -// Created by Milen on 05.09.23. -// Copyright © 2023 Marino Faggiana. All rights reserved. -// - -import SwiftUI - -struct NCMediaCellView: View { - let thumbnail: ScaledThumbnail - let shrinkRatio: CGFloat - let outerProxy: GeometryProxy - @Binding var title: String - - var body: some View { - GeometryReader { geometry in - Image(uiImage: thumbnail.image) - .resizable() - // .scaledToFit() - // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in - if isInView(innerRect: rect, isIn: outerProxy) { - print(thumbnail.metadata.fileName) - title = thumbnail.metadata.fileName - } - } - } - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - } - - private func isInView(innerRect:CGRect, isIn outerProxy:GeometryProxy) -> Bool { - let innerOrigin = innerRect.origin.x - let imageWidth = innerRect.width - let scrollOrigin = outerProxy.frame(in: .global).origin.x - let scrollWidth = outerProxy.size.width - if innerOrigin + imageWidth < scrollOrigin + scrollWidth && innerOrigin + imageWidth > scrollOrigin || - innerOrigin + imageWidth > scrollOrigin && innerOrigin < scrollOrigin + scrollWidth { - return true - } - return false - } -} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 271688b907..b846c65e70 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -10,6 +10,7 @@ import SwiftUI import PreviewSnapshots import NextcloudKit import FlowGrid +import VisibilityTrackingScrollView class NCMediaUIHostingController: UIHostingController { required init?(coder aDecoder: NSCoder) { @@ -22,23 +23,20 @@ struct NCMediaNew: View { @State var columns = 2 @State var title = "" + @State private var visibleMetadatas: [String] = [] + public static let scrollViewCoordinateSpace = "scrollView" var body: some View { GeometryReader { outerProxy in ZStack(alignment: .top) { - ScrollView { + VisibilityTrackingScrollView(action: handleVisibilityChanged) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) -// .onChange(of: geometry.frame(in: .local)) { rect in -// if isInView(innerRect: rect, isIn: outerProxy) { -// print("WOW") -// } -// } + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) } } - }.coordinateSpace(name: NCMediaNew.scrollViewCoordinateSpace) + } HStack(content: { Text(title) @@ -48,6 +46,9 @@ struct NCMediaNew: View { .frame(maxWidth: .infinity) .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } + .onChange(of: visibleMetadatas) { metadata in + title = metadata[0] + } .onRotate { orientation in if orientation.isLandscapeHardCheck { columns = 6 @@ -58,9 +59,15 @@ struct NCMediaNew: View { } } + func handleVisibilityChanged(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { + DispatchQueue.main.async { + if let date = tracker.topVisibleView, !date.isEmpty { + title = date + } + } + } } - struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 232528e865..e79df6dbe5 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -8,6 +8,7 @@ import SwiftUI import PreviewSnapshots +import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] @@ -23,7 +24,7 @@ struct NCMediaRow: View { ProgressView() } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCellView(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) } } } From 1cb48e2c1693f2583d644c7b8b01b8dc462965e7 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 13:42:47 +0200 Subject: [PATCH 050/103] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 29 -------------------------- iOSClient/Media/NCMediaNew.swift | 2 +- iOSClient/Media/NCMediaRow.swift | 3 +-- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 9005b3b39e..1c0ce61946 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -12,27 +12,12 @@ import VisibilityTrackingScrollView struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat - let outerProxy: GeometryProxy - @Binding var title: String -// @State private var visibleIndex: Set = [0,1] - @State private var visibleMetadata: IsCellVisiblePreferenceKey.PreferenceValue? = nil - var body: some View { ZStack { GeometryReader { geometry in Image(uiImage: thumbnail.image) .resizable() - // .scaledToFit() - // .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) -// .onChange(of: geometry.frame(in: .named(NCMediaNew.scrollViewCoordinateSpace))) { rect in -// if isInView(innerRect: rect, isIn: outerProxy) { -// visibleMetadata = .init(isVisible: true, metadata: thumbnail.metadata) -// } else { -// visibleMetadata = .init(isVisible: false, metadata: thumbnail.metadata) -// } -// } -// .preference(key: IsCellVisiblePreferenceKey.self, value: visibleMetadata) } .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) @@ -41,17 +26,3 @@ struct NCMediaCell: View { } } } - -struct IsCellVisiblePreferenceKey: PreferenceKey { - struct PreferenceValue: Equatable { - let isVisible: Bool - let metadata: tableMetadata - } - - typealias Value = PreferenceValue? - static var defaultValue: Value = nil - - static func reduce(value: inout Value, nextValue: () -> Value) { - value = value ?? nextValue() - } -} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index b846c65e70..4ca0ed559f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -33,7 +33,7 @@ struct NCMediaNew: View { VisibilityTrackingScrollView(action: handleVisibilityChanged) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, title: $title) + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) } } } diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index e79df6dbe5..0356329634 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -13,7 +13,6 @@ import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy - @Binding var title: String @StateObject private var viewModel = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -24,7 +23,7 @@ struct NCMediaRow: View { ProgressView() } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio, outerProxy: geometryProxy, title: $title) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) } } } From 0227e6c71bef9a294173c76e504616d47a064bbe Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 16:49:14 +0200 Subject: [PATCH 051/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 31 +++++++++++++------------- iOSClient/Media/NCMediaViewModel.swift | 6 +++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 4ca0ed559f..97b002091d 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -19,35 +19,34 @@ class NCMediaUIHostingController: UIHostingController { } struct NCMediaNew: View { - @StateObject private var viewModel = NCMediaViewModel() + @StateObject private var vm = NCMediaViewModel() @State var columns = 2 @State var title = "" - @State private var visibleMetadatas: [String] = [] - - public static let scrollViewCoordinateSpace = "scrollView" - var body: some View { GeometryReader { outerProxy in ZStack(alignment: .top) { VisibilityTrackingScrollView(action: handleVisibilityChanged) { LazyVStack(alignment: .leading, spacing: 2) { - ForEach(viewModel.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) } + + if vm.needsLoadingMoreItems { + ProgressView() + .frame(maxWidth: .infinity) + .onAppear { vm.loadMoreItems() } + } } } - HStack(content: { - Text(title) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(.white) - }) - .frame(maxWidth: .infinity) - .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) - } - .onChange(of: visibleMetadatas) { metadata in - title = metadata[0] +// HStack(content: { +// Text(title) +// .font(.system(size: 20, weight: .bold)) +// .foregroundStyle(.white) +// }) +// .frame(maxWidth: .infinity) +// .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } .onRotate { orientation in if orientation.isLandscapeHardCheck { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index c74dcece2b..a55d4067ec 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -21,6 +21,8 @@ import NextcloudKit internal var filterClassTypeImage = false internal var filterClassTypeVideo = false + internal var needsLoadingMoreItems = true + init() { reloadDataSourceWithCompletion { _ in } @@ -86,6 +88,10 @@ import NextcloudKit break } } + + func loadMoreItems() { + + } } // MARK: Notifications From d467029c0836a60f8549781d1bbdd90541fdda02 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 12 Sep 2023 17:37:01 +0200 Subject: [PATCH 052/103] Reload when switching accounts Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 2 +- iOSClient/Media/NCMediaNew.swift | 27 +++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 1c0ce61946..cca622121e 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -22,7 +22,7 @@ struct NCMediaCell: View { .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) +// Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 97b002091d..6b3c466fc9 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -40,22 +40,23 @@ struct NCMediaNew: View { } } -// HStack(content: { -// Text(title) -// .font(.system(size: 20, weight: .bold)) -// .foregroundStyle(.white) -// }) -// .frame(maxWidth: .infinity) -// .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + HStack(content: { + Text(title) + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(.white) + }) + .frame(maxWidth: .infinity) + .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } - .onRotate { orientation in - if orientation.isLandscapeHardCheck { - columns = 6 - } else { - columns = 2 - } + } + .onRotate { orientation in + if orientation.isLandscapeHardCheck { + columns = 6 + } else { + columns = 2 } } + .onAppear { vm.reloadDataSourceWithCompletion {_ in } } } func handleVisibilityChanged(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { From 190322e1625796f20cf38b7436ec3792c8752245 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 13 Sep 2023 18:15:49 +0200 Subject: [PATCH 053/103] Add shimmer and old and new media loading Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 32 ++--- iOSClient/Media/Cell/NCMediaCell.swift | 36 ++++- iOSClient/Media/NCMediaNew.swift | 9 +- iOSClient/Media/NCMediaRow.swift | 7 +- iOSClient/Media/NCMediaViewModel.swift | 183 +++++++++++++++++++++---- 5 files changed, 214 insertions(+), 53 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 1dc8666097..562a23ec51 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -116,7 +116,6 @@ F343A4BF2A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; F343A4C12A1E734600DDA874 /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F343A4BA2A1E734600DDA874 /* Optional+Extension.swift */; }; - F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F34624522AA08C5700FAA7B1 /* FlowGrid */; }; F359D8672A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8682A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; F359D8692A7D03420023F405 /* NCUtility+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F359D8662A7D03420023F405 /* NCUtility+Exif.swift */; }; @@ -136,6 +135,7 @@ F3E3B3B92AA73925006D08F5 /* NCMediaRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3B82AA73925006D08F5 /* NCMediaRowViewModel.swift */; }; F3E3B3BB2AA7396F006D08F5 /* NCMediaCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BA2AA7396F006D08F5 /* NCMediaCell.swift */; }; F3E3B3BD2AA73D9E006D08F5 /* NCMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */; }; + F3EFBF5F2AB2100E00B5724C /* Shimmer in Frameworks */ = {isa = PBXBuildFile; productRef = F3EFBF5E2AB2100E00B5724C /* Shimmer */; }; F3F7ACFE2A98C56C00AE12CF /* NCMediaNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */; }; F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; }; @@ -1504,7 +1504,7 @@ F76DA966277B76F30082465B /* UICKeyChainStore in Frameworks */, F7792DE529EEE02D005930CE /* MobileVLCKit.xcframework in Frameworks */, F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */, - F34624532AA08C5700FAA7B1 /* FlowGrid in Frameworks */, + F3EFBF5F2AB2100E00B5724C /* Shimmer in Frameworks */, F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */, F76DA963277B760E0082465B /* Queuer in Frameworks */, F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, @@ -2908,8 +2908,8 @@ F7A1050D29E587AF00FFD92B /* TagListView */, F31F69632A2F929600162F76 /* PreviewSnapshots */, F7F623B42A5EF4D30022D3D4 /* Gzip */, - F34624522AA08C5700FAA7B1 /* FlowGrid */, F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, + F3EFBF5E2AB2100E00B5724C /* Shimmer */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3085,8 +3085,8 @@ F31F69622A2F929600162F76 /* XCRemoteSwiftPackageReference "swiftui-preview-snapshots" */, F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */, F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, - F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */, F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, + F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4937,20 +4937,20 @@ version = 1.4.0; }; }; - F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */ = { + F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/rdev/FlowGrid.git"; + repositoryURL = "https://github.com/elegantchaos/VisibilityTrackingScrollView"; requirement = { kind = exactVersion; - version = 1.0.0; + version = 1.0.3; }; }; - F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */ = { + F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/elegantchaos/VisibilityTrackingScrollView"; + repositoryURL = "https://github.com/markiv/SwiftUI-Shimmer.git"; requirement = { - kind = exactVersion; - version = 1.0.3; + kind = upToNextMajorVersion; + minimumVersion = 1.4.0; }; }; F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { @@ -5245,16 +5245,16 @@ package = F31F69672A2F92F000162F76 /* XCRemoteSwiftPackageReference "SnapshotTestingHEIC" */; productName = SnapshotTestingHEIC; }; - F34624522AA08C5700FAA7B1 /* FlowGrid */ = { - isa = XCSwiftPackageProductDependency; - package = F383599C2AA08AFE00FA0B98 /* XCRemoteSwiftPackageReference "FlowGrid" */; - productName = FlowGrid; - }; F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */ = { isa = XCSwiftPackageProductDependency; package = F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */; productName = VisibilityTrackingScrollView; }; + F3EFBF5E2AB2100E00B5724C /* Shimmer */ = { + isa = XCSwiftPackageProductDependency; + package = F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */; + productName = Shimmer; + }; F70716F829881CFA00E72C1D /* UICKeyChainStore */ = { isa = XCSwiftPackageProductDependency; package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index cca622121e..adf3a1b268 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -8,6 +8,7 @@ import SwiftUI import VisibilityTrackingScrollView +import Shimmer struct NCMediaCell: View { let thumbnail: ScaledThumbnail @@ -15,14 +16,35 @@ struct NCMediaCell: View { var body: some View { ZStack { - GeometryReader { geometry in - Image(uiImage: thumbnail.image) - .resizable() - } - .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + Image(uiImage: thumbnail.image) + .resizable() + .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) -// Text(thumbnail.metadata.fileName).lineLimit(1).foregroundColor(.white) +// Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) + } + } +} + +struct NCMediaLoadingCell: View { + let height: CGFloat + let itemsInRow: Int + let metadata: tableMetadata + + let gradient = Gradient(colors: [ + .black.opacity(0.4), + .black.opacity(0.7), + .black.opacity(0.4) + ]) + + var body: some View { + ZStack { + Image(uiImage: UIImage()) + .resizable() + .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "") + .frame(width: UIScreen.main.bounds.width / CGFloat(itemsInRow), height: 130) + .redacted(reason: .placeholder) + .shimmering(gradient: gradient, bandSize: 0.7) } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 6b3c466fc9..90e2dd1db3 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -9,7 +9,6 @@ import SwiftUI import PreviewSnapshots import NextcloudKit -import FlowGrid import VisibilityTrackingScrollView class NCMediaUIHostingController: UIHostingController { @@ -26,7 +25,7 @@ struct NCMediaNew: View { var body: some View { GeometryReader { outerProxy in ZStack(alignment: .top) { - VisibilityTrackingScrollView(action: handleVisibilityChanged) { + VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) @@ -45,7 +44,7 @@ struct NCMediaNew: View { .font(.system(size: 20, weight: .bold)) .foregroundStyle(.white) }) - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity ) .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } @@ -56,10 +55,10 @@ struct NCMediaNew: View { columns = 2 } } - .onAppear { vm.reloadDataSourceWithCompletion {_ in } } + .onAppear { vm.loadData() } } - func handleVisibilityChanged(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { + func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { DispatchQueue.main.async { if let date = tracker.topVisibleView, !date.isEmpty { title = date diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 0356329634..eab354668a 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -20,7 +20,11 @@ struct NCMediaRow: View { var body: some View { HStack(spacing: spacing) { if viewModel.rowData.scaledThumbnails.isEmpty { - ProgressView() + let randomHeight = CGFloat.random(in: 150...300) + + ForEach(metadatas, id: \.self) { metadata in + NCMediaLoadingCell(height: randomHeight, itemsInRow: metadatas.count, metadata: metadata) + } } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) @@ -33,4 +37,3 @@ struct NCMediaRow: View { } } } - diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index a55d4067ec..38ccb1c977 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -24,8 +24,6 @@ import NextcloudKit internal var needsLoadingMoreItems = true init() { - reloadDataSourceWithCompletion { _ in } - NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(moveFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterMoveFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) @@ -41,7 +39,7 @@ import NextcloudKit NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) } - @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) { + func loadData() { guard let appDelegate, !appDelegate.account.isEmpty else { return } if account != appDelegate.account { @@ -52,7 +50,7 @@ import NextcloudKit self.queryDB(isForced: true) } - func queryDB(isForced: Bool = false) { + private func queryDB(isForced: Bool = false) { guard let appDelegate else { return } livePhoto = CCUtility.getLivePhoto() @@ -75,22 +73,25 @@ import NextcloudKit guard let predicate = predicate else { return } - metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) - - switch CCUtility.getMediaSortDate() { - case "date": - self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) - case "creationDate": - self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) - case "uploadDate": - self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) - default: - break + DispatchQueue.main.async { + self.metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicate, livePhoto: self.livePhoto) + + switch CCUtility.getMediaSortDate() { + case "date": + self.metadatas = self.metadatas.sorted(by: {($0.date as Date) > ($1.date as Date)}) + case "creationDate": + self.metadatas = self.metadatas.sorted(by: {($0.creationDate as Date) > ($1.creationDate as Date)}) + case "uploadDate": + self.metadatas = self.metadatas.sorted(by: {($0.uploadDate as Date) > ($1.uploadDate as Date)}) + default: + break + } } } func loadMoreItems() { - + searchOldMedia() + searchNewMedia() } } @@ -98,7 +99,6 @@ import NextcloudKit extension NCMediaViewModel { @objc func deleteFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, let error = userInfo["error"] as? NKError else { return } let onlyLocalCache: Bool = userInfo["onlyLocalCache"] as? Bool ?? false @@ -124,7 +124,6 @@ extension NCMediaViewModel { } @objc func moveFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary? else { return } // if let hud = userInfo["hud"] as? JGProgressHUD { @@ -133,22 +132,19 @@ extension NCMediaViewModel { } @objc func copyFile(_ notification: NSNotification) { - moveFile(notification) } @objc func renameFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, let account = userInfo["account"] as? String, account == appDelegate?.account else { return } - self.reloadDataSourceWithCompletion { _ in } + self.loadData() } @objc func uploadedFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary?, let error = userInfo["error"] as? NKError, error == .success, @@ -156,6 +152,147 @@ extension NCMediaViewModel { account == appDelegate?.account else { return } - self.reloadDataSourceWithCompletion { _ in } + self.loadData() + } +} + +// MARK: - Search media + +extension NCMediaViewModel { + private func searchOldMedia(value: Int = -30, limit: Int = 300) { + +// if oldInProgress { return } else { oldInProgress = true } +// DispatchQueue.main.async { +// self.collectionView.reloadData() +// var bottom: CGFloat = 0 +// if let mainTabBar = self.tabBarController?.tabBar as? NCMainTabBar { +// bottom = -mainTabBar.getHight() +// } +// NCActivityIndicator.shared.start(backgroundView: self.view, bottom: bottom - 5, style: .medium) +// } + + var lessDate = Date() + if predicateDefault != nil { + if let metadata = NCManageDatabase.shared.getMetadata(predicate: predicateDefault!, sorted: "date", ascending: true) { + lessDate = metadata.date as Date + } + } + + var greaterDate: Date + if value == -999 { + greaterDate = Date.distantPast + } else { + greaterDate = Calendar.current.date(byAdding: .day, value: value, to: lessDate)! + } + + let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.searchMedia(path: mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in +// +//// self.oldInProgress = false +// DispatchQueue.main.async { +// NCActivityIndicator.shared.stop() +// self.loadData() +//// self.collectionView.reloadData() +// } + + if error == .success && account == self.appDelegate?.account { + if !files.isEmpty { + NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in + let predicateDate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) + let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateDate, self.predicateDefault!]) + let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) + let metadatasChanged = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) + if metadatasChanged.metadatasUpdate.isEmpty { + self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: true) + } else { + self.loadData() + } + } + } else { + self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: false) + } + } else if error != .success { + NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Media search old media error code \(error.errorCode) " + error.errorDescription) + } + } + } + + private func researchOldMedia(value: Int, limit: Int, withElseReloadDataSource: Bool) { + + if value == -30 { + searchOldMedia(value: -90) + } else if value == -90 { + searchOldMedia(value: -180) + } else if value == -180 { + searchOldMedia(value: -999) + } else if value == -999 && limit > 0 { + searchOldMedia(value: -999, limit: 0) + } else { + if withElseReloadDataSource { + loadData() + } + } + } + +// @objc func searchNewMediaTimer() { +// self.searchNewMedia() +// } +// + @objc func searchNewMedia() { + +// if newInProgress { return } else { +// newInProgress = true +// mediaCommandView?.activityIndicator.startAnimating() +// } + + var limit: Int = 1000 + guard var lessDate = Calendar.current.date(byAdding: .second, value: 1, to: Date()) else { return } + guard var greaterDate = Calendar.current.date(byAdding: .day, value: -30, to: Date()) else { return } + +// if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) { +// if let cell = visibleCells.first as? NCGridMediaCell { +// if cell.date != nil { +// if cell.date != self.metadatas.first?.date as Date? { +// lessDate = Calendar.current.date(byAdding: .second, value: 1, to: cell.date!)! +// limit = 0 +// } +// } +// } +// if let cell = visibleCells.last as? NCGridMediaCell { +// if cell.date != nil { +// greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: cell.date!)! +// } +// } +// } + +// reloadDataThenPerform { + + let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, data, error in + +// self.newInProgress = false +// DispatchQueue.main.async { +// self.mediaCommandView?.activityIndicator.stopAnimating() +// } + + if error == .success && account == self.appDelegate?.account && files.count > 0 { + NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in + let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) + let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.predicate!]) + let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) + let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) + if updateMetadatas.metadatasUpdate.count > 0 || updateMetadatas.metadatasDelete.count > 0 { + self.loadData() + } + } + } else if error == .success && files.count == 0 && self.metadatas.count == 0 { + self.searchOldMedia() + } else if error != .success { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) + } + } +// } } } From f0e1949dd09be8b03927744e469b5015b2400cf4 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 13 Sep 2023 18:47:36 +0200 Subject: [PATCH 054/103] Work on top section Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 9 +++++---- iOSClient/Media/NCMediaNew.swift | 23 ++++++++++++++++++----- iOSClient/Media/NCMediaRow.swift | 4 +--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index adf3a1b268..d5e6f81fbe 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -21,15 +21,16 @@ struct NCMediaCell: View { .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) -// Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) + // Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) } } } struct NCMediaLoadingCell: View { - let height: CGFloat let itemsInRow: Int let metadata: tableMetadata + let geometryProxy: GeometryProxy + let spacing: CGFloat let gradient = Gradient(colors: [ .black.opacity(0.4), @@ -41,8 +42,8 @@ struct NCMediaLoadingCell: View { ZStack { Image(uiImage: UIImage()) .resizable() - .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "") - .frame(width: UIScreen.main.bounds.width / CGFloat(itemsInRow), height: 130) + .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "")// TODO: Fix spacing + .frame(width: (geometryProxy.size.width - spacing) / CGFloat(itemsInRow), height: 130) .redacted(reason: .placeholder) .shimmering(gradient: gradient, bandSize: 0.7) } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 90e2dd1db3..b01bdd4e43 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -20,7 +20,7 @@ class NCMediaUIHostingController: UIHostingController { struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @State var columns = 2 - @State var title = "" + @State var title = "Placeholder" var body: some View { GeometryReader { outerProxy in @@ -37,14 +37,27 @@ struct NCMediaNew: View { .onAppear { vm.loadMoreItems() } } } + .padding(.top, 70) + .padding(.bottom, 30) } HStack(content: { - Text(title) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(.white) + HStack { + Text(title) + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(.white) + Spacer() + Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Text("Select") + }) + Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Image(systemName: "ellipsis") + }) + } }) - .frame(maxWidth: .infinity ) + .frame(maxWidth: .infinity) + .padding(.horizontal, 10) + .padding(.vertical, 20) .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index eab354668a..26b27ffc60 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -20,10 +20,8 @@ struct NCMediaRow: View { var body: some View { HStack(spacing: spacing) { if viewModel.rowData.scaledThumbnails.isEmpty { - let randomHeight = CGFloat.random(in: 150...300) - ForEach(metadatas, id: \.self) { metadata in - NCMediaLoadingCell(height: randomHeight, itemsInRow: metadatas.count, metadata: metadata) + NCMediaLoadingCell(itemsInRow: metadatas.count, metadata: metadata, geometryProxy: geometryProxy, spacing: spacing) } } else { ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in From 71b967a481b3ba05639d408daab7b37bf232cbc6 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 14 Sep 2023 15:08:48 +0200 Subject: [PATCH 055/103] Mark no thumbnails Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 33 ++++++++++++++++++----- iOSClient/Media/NCMediaRowViewModel.swift | 25 +++++++++-------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index d5e6f81fbe..ec2b06f16c 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -15,14 +15,35 @@ struct NCMediaCell: View { let shrinkRatio: CGFloat var body: some View { - ZStack { - Image(uiImage: thumbnail.image) - .resizable() - .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + var image = Image(uiImage: thumbnail.image) + .resizable() + .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + + ZStack(alignment: .bottomLeading) { + ZStack(alignment: .center) { + if thumbnail.isDefaultImage { + image + .foregroundColor(Color(uiColor: .systemGray4)) + .scaledToFit() + .frame(width: 40) + } else { + image + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) - // Text(CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date)).lineLimit(1).foregroundColor(.white) + if thumbnail.metadata.isVideo { + Image(systemName: "play.fill") + .resizable() + .foregroundColor(Color(uiColor: .systemGray4)) + .scaledToFit() + .frame(width: 20) + .padding(.leading, 10) + .padding(.bottom, 10) + } } + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + .background(Color(uiColor: .systemGray6)) } } diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift index e507735c50..8b069c9a74 100644 --- a/iOSClient/Media/NCMediaRowViewModel.swift +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -16,6 +16,7 @@ struct RowData { struct ScaledThumbnail: Hashable { let image: UIImage + var isDefaultImage = false var scaledSize: CGSize = .zero let metadata: tableMetadata @@ -75,27 +76,29 @@ struct ScaledThumbnail: Hashable { sizeIcon: NCGlobal.shared.sizeIcon, etag: etagResource, options: options) { _, _, imageIcon, _, etag, error in + print(metadata.isVideo.description + " " + metadata.fileName) if error == .success, let image = imageIcon { NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) - DispatchQueue.main.async { - thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) + } else { + thumbnails.append(ScaledThumbnail(image: UIImage(systemName: metadata.isVideo ? "video.fill" : "photo.fill")!.withRenderingMode(.alwaysTemplate), isDefaultImage: true, metadata: metadata)) + } - if thumbnails.count == self.metadatas.count { - thumbnails.enumerated().forEach { index, thumbnail in - thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) - } + DispatchQueue.main.async { + if thumbnails.count == self.metadatas.count { + thumbnails.enumerated().forEach { index, thumbnail in + thumbnails[index].scaledSize = self.getScaledThumbnailSize(of: thumbnail, thumbnailsInRow: thumbnails) + } - let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) + let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) - self.rowData.scaledThumbnails = thumbnails - self.rowData.shrinkRatio = shrinkRatio - } + self.rowData.scaledThumbnails = thumbnails + self.rowData.shrinkRatio = shrinkRatio } } } } } - } func getScaledThumbnailSize(of thumbnail: ScaledThumbnail, thumbnailsInRow thumbnails: [ScaledThumbnail]) -> CGSize { From d2ebc67a5cd19b580c71e8221c2c128232ddd7c5 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 17:28:08 +0200 Subject: [PATCH 056/103] Add pull to refresh Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 17 ++++++ iOSClient/Main/Main.storyboard | 33 +----------- iOSClient/Media/Cell/NCMediaCell.swift | 29 ++++++++-- iOSClient/Media/NCMediaNew.swift | 54 +++++++++++++++++-- iOSClient/Media/NCMediaRow.swift | 13 ++--- iOSClient/Media/NCMediaViewModel.swift | 9 ++++ .../NCViewerMedia/NCViewerMediaPage.swift | 2 + 7 files changed, 112 insertions(+), 45 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 562a23ec51..46bf985521 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; + F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6BEF2AB4AAB700148857 /* Refreshable */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -1507,6 +1508,7 @@ F3EFBF5F2AB2100E00B5724C /* Shimmer in Frameworks */, F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */, F76DA963277B760E0082465B /* Queuer in Frameworks */, + F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */, F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, F70B86752642CE3B00ED5349 /* FirebaseCrashlytics in Frameworks */, F7A1050E29E587AF00FFD92B /* TagListView in Frameworks */, @@ -2910,6 +2912,7 @@ F7F623B42A5EF4D30022D3D4 /* Gzip */, F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, F3EFBF5E2AB2100E00B5724C /* Shimmer */, + F30A6BEF2AB4AAB700148857 /* Refreshable */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3087,6 +3090,7 @@ F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, + F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4913,6 +4917,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/c-villain/Refreshable"; + requirement = { + kind = exactVersion; + version = 0.2.0; + }; + }; F31F694B2A2F6EFA00162F76 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; @@ -5160,6 +5172,11 @@ package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; productName = UICKeyChainStore; }; + F30A6BEF2AB4AAB700148857 /* Refreshable */ = { + isa = XCSwiftPackageProductDependency; + package = F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */; + productName = Refreshable; + }; F30A96332A2DFCD000D7BCFE /* Realm */ = { isa = XCSwiftPackageProductDependency; package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index 3e3f022757..557c9918c1 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -1,22 +1,12 @@ - + - + - - - - - - - - - - @@ -80,25 +70,6 @@ - - - - - - - - - - - - - - - - - - - diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index ec2b06f16c..5b5f84c1c6 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -9,13 +9,16 @@ import SwiftUI import VisibilityTrackingScrollView import Shimmer +import NextcloudKit struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat + let onTap: (ScaledThumbnail) -> Void + var body: some View { - var image = Image(uiImage: thumbnail.image) + let image = Image(uiImage: thumbnail.image) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") @@ -38,12 +41,22 @@ struct NCMediaCell: View { .foregroundColor(Color(uiColor: .systemGray4)) .scaledToFit() .frame(width: 20) - .padding(.leading, 10) - .padding(.bottom, 10) + .padding([.leading, .bottom], 10) } } .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) .background(Color(uiColor: .systemGray6)) + .onTapGesture { + onTap(thumbnail) + } + } +} + +struct NCMediaCell_Previews: PreviewProvider { + static var previews: some View { + let mockMetadata = tableMetadata() + + NCMediaCell(thumbnail: .init(image: UIImage(systemName: "video.fill")!, metadata: mockMetadata), shrinkRatio: 1, onTap: { _ in }) } } @@ -70,3 +83,13 @@ struct NCMediaLoadingCell: View { } } } + +struct NCMediaLoadingCell_Previews: PreviewProvider { + static var previews: some View { + let mockMetadata = tableMetadata() + + GeometryReader { proxy in + NCMediaLoadingCell(itemsInRow: 1, metadata: tableMetadata(), geometryProxy: proxy, spacing: 2) + } + } +} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index b01bdd4e43..46d42ade24 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -17,10 +17,44 @@ class NCMediaUIHostingController: UIHostingController { } } +struct NCViewerMediaPageController: UIViewControllerRepresentable { + let metadatas: [tableMetadata] + let selectedMetadata: tableMetadata + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> UINavigationController { + + if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { + var index = 0 + for medatasImage in metadatas { + if medatasImage.ocId == selectedMetadata.ocId { + viewController.currentIndex = index + break + } + index += 1 + } + viewController.metadatas = metadatas + + return UINavigationController(rootViewController: viewController) + } else { + // Handle the case where the cast fails, such as returning a default view controller or showing an error. + // You can also return an empty UIViewController() as a fallback. + return UINavigationController() // Replace with your fallback or error handling logic + } + } + + func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) { + + } +} + struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() - @State var columns = 2 - @State var title = "Placeholder" + @State private var columns = 2 + @State private var title = "" + + @State private var isMediaViewControllerPresented = false + + @State private var selectedMetadata = tableMetadata() var body: some View { GeometryReader { outerProxy in @@ -28,7 +62,10 @@ struct NCMediaNew: View { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) { tappedThumbnail in + selectedMetadata = tappedThumbnail.metadata + isMediaViewControllerPresented = true + } } if vm.needsLoadingMoreItems { @@ -39,6 +76,10 @@ struct NCMediaNew: View { } .padding(.top, 70) .padding(.bottom, 30) + + } + .refreshable { + vm.onPullToRefresh() } HStack(content: { @@ -47,10 +88,10 @@ struct NCMediaNew: View { .font(.system(size: 20, weight: .bold)) .foregroundStyle(.white) Spacer() - Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Button(action: {}, label: { Text("Select") }) - Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: { + Button(action: {}, label: { Image(systemName: "ellipsis") }) } @@ -69,6 +110,9 @@ struct NCMediaNew: View { } } .onAppear { vm.loadData() } + .fullScreenCover(isPresented: $isMediaViewControllerPresented) { + NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: selectedMetadata) + } } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 26b27ffc60..ee312d7852 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -13,25 +13,26 @@ import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy + let onCellTap: (ScaledThumbnail) -> Void - @StateObject private var viewModel = NCMediaRowViewModel() + @StateObject private var vm = NCMediaRowViewModel() private let spacing: CGFloat = 2 var body: some View { HStack(spacing: spacing) { - if viewModel.rowData.scaledThumbnails.isEmpty { + if vm.rowData.scaledThumbnails.isEmpty { ForEach(metadatas, id: \.self) { metadata in NCMediaLoadingCell(itemsInRow: metadatas.count, metadata: metadata, geometryProxy: geometryProxy, spacing: spacing) } } else { - ForEach(viewModel.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: viewModel.rowData.shrinkRatio) + ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, onTap: onCellTap) } } } .onAppear { - viewModel.configure(metadatas: metadatas) - viewModel.downloadThumbnails(rowWidth: geometryProxy.size.width, spacing: spacing) + vm.configure(metadatas: metadatas) + vm.downloadThumbnails(rowWidth: geometryProxy.size.width, spacing: spacing) } } } diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 38ccb1c977..d594b6b8b4 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -29,6 +29,8 @@ import NextcloudKit NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) + + searchNewMedia() } deinit { @@ -91,8 +93,15 @@ import NextcloudKit func loadMoreItems() { searchOldMedia() + } + + func onPullToRefresh() { searchNewMedia() } + + func onCellTapped(metadata: tableMetadata) { + appDelegate?.activeServerUrl = metadata.serverUrl + } } // MARK: Notifications diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 7ec15b5c5d..efb8d07b3d 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -71,6 +71,8 @@ class NCViewerMediaPage: UIViewController { private lazy var moreNavigationItem = UIBarButtonItem(image: UIImage(named: "more")!.image(color: .label, size: 25), style: .plain, target: self, action: #selector(openMenuMore)) private lazy var imageDetailNavigationItem = UIBarButtonItem(image: UIImage(systemName: "info.circle")!.image(color: .label, size: 22), style: .plain, target: self, action: #selector(toggleDetail)) + var dismissAction: (() -> Void)? + // MARK: - View Life Cycle override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { From 063447c70c329b9cbf1e9fdd0e61ade5b8bffd51 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 18:00:04 +0200 Subject: [PATCH 057/103] Add animated color change on top bar Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 16 ++++++++++++++ iOSClient/Media/NCMediaNew.swift | 29 +++++++++++++++----------- iOSClient/Utility/PreferenceKeys.swift | 16 ++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 iOSClient/Utility/PreferenceKeys.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 46bf985521..0754305f64 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -77,6 +77,13 @@ D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6BEF2AB4AAB700148857 /* Refreshable */; }; + F30A6BF22AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF32AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF42AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF52AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF62AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF72AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BF82AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -840,6 +847,7 @@ C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesIntegrationTests.swift; sourceTree = ""; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; + F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceKeys.swift; sourceTree = ""; }; F30A96042A27299D00D7BCFE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = EnvVars.stencil; sourceTree = ""; }; F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EnvVars.generated.swift; path = Sourcery/EnvVars.generated.swift; sourceTree = ""; }; @@ -2262,6 +2270,7 @@ F702F2FC25EE5D2C008F8E80 /* NYMnemonic */, AF36077027BFA4E8001A243D /* ParallelWorker.swift */, F7245923289BB50B00474787 /* ThreadSafeDictionary.swift */, + F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */, ); path = Utility; sourceTree = ""; @@ -3368,6 +3377,7 @@ D575039F27146F93008DC9DC /* String+Extension.swift in Sources */, F769CA1A2966EA3C00039397 /* ComponentView.swift in Sources */, F757CC8829E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, + F30A6BF82AB4B31200148857 /* PreferenceKeys.swift in Sources */, F79B646326CA661600838ACA /* UIControl+Extension.swift in Sources */, F78A10C429322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */, F75CA1482962F13700B01130 /* HUDView.swift in Sources */, @@ -3436,6 +3446,7 @@ F7490E6E29882B56009DCE94 /* NCBrand.swift in Sources */, F7490E8129882C79009DCE94 /* NCManageDatabase+DashboardWidget.swift in Sources */, F7490E8629882C99009DCE94 /* NCUtilityFileSystem.swift in Sources */, + F30A6BF72AB4B31200148857 /* PreferenceKeys.swift in Sources */, F763D2A22A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */, F343A4C02A1E734600DDA874 /* Optional+Extension.swift in Sources */, F7490E8529882C8C009DCE94 /* NCManageDatabase+Video.swift in Sources */, @@ -3502,6 +3513,7 @@ F7F878AF1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */, AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */, F702F2D025EE5B5C008F8E80 /* NCGlobal.swift in Sources */, + F30A6BF52AB4B31200148857 /* PreferenceKeys.swift in Sources */, F343A4BE2A1E734600DDA874 /* Optional+Extension.swift in Sources */, F7EDE4DB262D7BA200414FE6 /* NCCellProtocol.swift in Sources */, F72944F62A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */, @@ -3577,6 +3589,7 @@ F77ED59328C9CEA000E24ED0 /* ToolbarWidgetProvider.swift in Sources */, F72A17D828B221E300F3F159 /* DashboardWidgetView.swift in Sources */, F77ED59528C9CEA400E24ED0 /* ToolbarWidgetView.swift in Sources */, + F30A6BF32AB4B31200148857 /* PreferenceKeys.swift in Sources */, F78302FB28B4C3EE00B84583 /* NCManageDatabase+Video.swift in Sources */, F72EA95228B7BA2A00C88F0C /* DashboardWidgetProvider.swift in Sources */, F343A4BC2A1E734600DDA874 /* Optional+Extension.swift in Sources */, @@ -3619,6 +3632,7 @@ F7D68FCF28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */, F359D86B2A7D03420023F405 /* NCUtility+Exif.swift in Sources */, F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */, + F30A6BF62AB4B31200148857 /* PreferenceKeys.swift in Sources */, AF4BF61B27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, F70460542499095400BB98A7 /* NotificationCenter+MainThread.swift in Sources */, F78A10C329322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */, @@ -3686,6 +3700,7 @@ F769454622E9F1B0000A798A /* NCShareCommon.swift in Sources */, F70753F12542A9A200972D44 /* NCViewerMedia.swift in Sources */, F78A18B823CDE2B300F681F3 /* NCViewerRichWorkspace.swift in Sources */, + F30A6BF22AB4B31200148857 /* PreferenceKeys.swift in Sources */, F7A60F86292D215000FCE1F2 /* NCShareAccounts.swift in Sources */, F77910AB25DD53C700CEDB9E /* NCSettingsBundleHelper.swift in Sources */, AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, @@ -3862,6 +3877,7 @@ F343A4BD2A1E734600DDA874 /* Optional+Extension.swift in Sources */, F7A8D73528F17E16008BBE1C /* NCManageDatabase.swift in Sources */, F7A8D74428F1827B008BBE1C /* ThreadSafeDictionary.swift in Sources */, + F30A6BF42AB4B31200148857 /* PreferenceKeys.swift in Sources */, F7C9739528F17131002C43E2 /* IntentHandler.swift in Sources */, F7A8D73D28F181D3008BBE1C /* NCUtilityFileSystem.swift in Sources */, F7A8D74528F1828E008BBE1C /* CCUtility.m in Sources */, diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 46d42ade24..97fcc7ef66 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -36,24 +36,19 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { return UINavigationController(rootViewController: viewController) } else { - // Handle the case where the cast fails, such as returning a default view controller or showing an error. - // You can also return an empty UIViewController() as a fallback. - return UINavigationController() // Replace with your fallback or error handling logic + return UINavigationController() } } - func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) { - - } + func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) {} } struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @State private var columns = 2 @State private var title = "" - + @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false - @State private var selectedMetadata = tableMetadata() var body: some View { @@ -76,17 +71,27 @@ struct NCMediaNew: View { } .padding(.top, 70) .padding(.bottom, 30) + .background(GeometryReader { geometry in + Color.clear + .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) + }) + .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in + withAnimation(.easeInOut) { + isScrolledToTop = value.y >= 0 + } + } } .refreshable { vm.onPullToRefresh() } + .coordinateSpace(name: "scroll") HStack(content: { HStack { Text(title) .font(.system(size: 20, weight: .bold)) - .foregroundStyle(.white) + .foregroundStyle(isScrolledToTop ? .black : .white) Spacer() Button(action: {}, label: { Text("Select") @@ -97,9 +102,9 @@ struct NCMediaNew: View { } }) .frame(maxWidth: .infinity) - .padding(.horizontal, 10) - .padding(.vertical, 20) - .background(LinearGradient(gradient: Gradient(colors: [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + .padding([.horizontal, .top], 10) + .padding(.bottom, 20) + .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } .onRotate { orientation in diff --git a/iOSClient/Utility/PreferenceKeys.swift b/iOSClient/Utility/PreferenceKeys.swift new file mode 100644 index 0000000000..5ddb42b507 --- /dev/null +++ b/iOSClient/Utility/PreferenceKeys.swift @@ -0,0 +1,16 @@ +// +// PreferenceKeys.swift +// Nextcloud +// +// Created by Milen on 15.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import SwiftUI + +struct ScrollOffsetPreferenceKey: PreferenceKey { + static var defaultValue: CGPoint = .zero + + static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {} +} From 56a7b22c615ba7db6e86a07b8c52d2db272c24c7 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 18:05:52 +0200 Subject: [PATCH 058/103] Adjust Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 97fcc7ef66..0097e85af3 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -77,7 +77,7 @@ struct NCMediaNew: View { }) .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in withAnimation(.easeInOut) { - isScrolledToTop = value.y >= 0 + isScrolledToTop = value.y >= -10 } } From e5ac7b87ac3f191ec7af98e75bf8e1ddff0134aa Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Sep 2023 18:12:38 +0200 Subject: [PATCH 059/103] Adjust Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 3 ++- iOSClient/Media/NCMediaViewModel.swift | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 0097e85af3..52781c129f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -67,10 +67,11 @@ struct NCMediaNew: View { ProgressView() .frame(maxWidth: .infinity) .onAppear { vm.loadMoreItems() } + .padding(.top, 10) } } .padding(.top, 70) - .padding(.bottom, 30) + .padding(.bottom, 40) .background(GeometryReader { geometry in Color.clear .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index d594b6b8b4..b4a824bfdd 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -93,6 +93,7 @@ import NextcloudKit func loadMoreItems() { searchOldMedia() +// needsLoadingMoreItems = false } func onPullToRefresh() { From 019f09b01bb41e3fd8450e3cfa5c7384d1f9323e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 18 Sep 2023 12:34:00 +0200 Subject: [PATCH 060/103] Add actions to some options Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 2 +- iOSClient/Media/Cell/NCMediaCell.swift | 5 ++++ iOSClient/Media/NCMediaNew.swift | 40 ++++++++++++++++++++++++-- iOSClient/Media/NCMediaViewModel.swift | 12 ++++++-- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 0754305f64..8a5ff22f5b 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -2401,8 +2401,8 @@ children = ( F720B5B72507B9A5008C94E5 /* Cell */, F7501C302212E57400FB1415 /* NCMedia.storyboard */, - F7501C312212E57400FB1415 /* NCMedia.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, + F7501C312212E57400FB1415 /* NCMedia.swift */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, F3E3B3BC2AA73D9E006D08F5 /* NCMediaViewModel.swift */, F3AEDCA62AA720F800FDFA44 /* NCMediaRow.swift */, diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 5b5f84c1c6..cb10e291f3 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -21,6 +21,11 @@ struct NCMediaCell: View { let image = Image(uiImage: thumbnail.image) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") + .contextMenu(ContextMenu(menuItems: { + Text("Menu Item 1") + Text("Menu Item 2") + Text("Menu Item 3") + })) ZStack(alignment: .bottomLeading) { ZStack(alignment: .center) { diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 52781c129f..b0110ed240 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -50,6 +50,7 @@ struct NCMediaNew: View { @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false @State private var selectedMetadata = tableMetadata() + @State private var sort: Int = 0 var body: some View { GeometryReader { outerProxy in @@ -97,9 +98,44 @@ struct NCMediaNew: View { Button(action: {}, label: { Text("Select") }) - Button(action: {}, label: { + Menu { + Section { + Button(action: { + vm.filterClassTypeImage = !vm.filterClassTypeImage + vm.filterClassTypeVideo = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") + }) + Button(action: { + vm.filterClassTypeVideo = !vm.filterClassTypeVideo + vm.filterClassTypeImage = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + }) + } + + Section { + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } + + Picker("Sorting options", selection: $sort) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + } + .pickerStyle(.menu) + + } label: { Image(systemName: "ellipsis") - }) + } } }) .frame(maxWidth: .infinity) diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index b4a824bfdd..33a5d0e2d2 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -7,6 +7,7 @@ // import NextcloudKit +import Combine @MainActor class NCMediaViewModel: ObservableObject { @Published var metadatas: [tableMetadata] = [] @@ -18,8 +19,10 @@ import NextcloudKit private var predicateDefault: NSPredicate? private var predicate: NSPredicate? private let appDelegate = UIApplication.shared.delegate as? AppDelegate - internal var filterClassTypeImage = false - internal var filterClassTypeVideo = false + @Published internal var filterClassTypeImage = false + @Published internal var filterClassTypeVideo = false + + private var cancellables: Set = [] internal var needsLoadingMoreItems = true @@ -31,6 +34,9 @@ import NextcloudKit NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) searchNewMedia() + + $filterClassTypeImage.sink { _ in self.loadData() }.store(in: &cancellables) + $filterClassTypeVideo.sink{ _ in self.loadData() }.store(in: &cancellables) } deinit { @@ -93,7 +99,7 @@ import NextcloudKit func loadMoreItems() { searchOldMedia() -// needsLoadingMoreItems = false + needsLoadingMoreItems = false } func onPullToRefresh() { From a46c3121c0b881f51962cb8b0bf75c03e8e3116c Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 18 Sep 2023 15:47:41 +0200 Subject: [PATCH 061/103] Design work Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index b0110ed240..0411dae1b1 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -46,7 +46,7 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @State private var columns = 2 - @State private var title = "" + @State private var title = "Media" @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false @State private var selectedMetadata = tableMetadata() @@ -93,11 +93,16 @@ struct NCMediaNew: View { HStack { Text(title) .font(.system(size: 20, weight: .bold)) - .foregroundStyle(isScrolledToTop ? .black : .white) + .foregroundStyle(isScrolledToTop ? Color.primary : .white) Spacer() Button(action: {}, label: { - Text("Select") + Text("Select").font(.system(size: 14)) + .foregroundStyle(isScrolledToTop ? .blue : .white) }) + .padding(.horizontal, 6) + .padding(.vertical, 3) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) Menu { Section { Button(action: { @@ -132,10 +137,15 @@ struct NCMediaNew: View { Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) } .pickerStyle(.menu) - } label: { - Image(systemName: "ellipsis") - } + Image(systemName: "ellipsis").font(.system(size: 15)) + .padding(.horizontal, 2) + .padding(.vertical, 8) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) + .foregroundColor(isScrolledToTop ? Color.blue : .white) + + } } }) .frame(maxWidth: .infinity) From a2ad767172fd6785b60dbfdf96b8038a2b649721 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 12:35:11 +0200 Subject: [PATCH 062/103] WIP with pushing VCs (kill me) Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 90 +++++---- iOSClient/Media/NCMediaNew.swift | 265 +++++++++++++++---------- iOSClient/Media/NCMediaRow.swift | 6 +- 3 files changed, 208 insertions(+), 153 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index cb10e291f3..075291252e 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -14,8 +14,9 @@ import NextcloudKit struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat - - let onTap: (ScaledThumbnail) -> Void + @Binding var isInSelectMode: Bool + let onTap: (ScaledThumbnail, Bool) -> Void + @State private var isTappedInSelectMode = false var body: some View { let image = Image(uiImage: thumbnail.image) @@ -27,42 +28,57 @@ struct NCMediaCell: View { Text("Menu Item 3") })) - ZStack(alignment: .bottomLeading) { - ZStack(alignment: .center) { - if thumbnail.isDefaultImage { - image + ZStack(alignment: .center) { +// NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { + if thumbnail.isDefaultImage { + image + .foregroundColor(Color(uiColor: .systemGray4)) + .scaledToFit() + .frame(width: 40) + } else { + image + } +// } + + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .overlay(alignment: .bottomLeading) { + if thumbnail.metadata.isVideo, !thumbnail.isDefaultImage { + Image(systemName: "play.fill") + .resizable() .foregroundColor(Color(uiColor: .systemGray4)) .scaledToFit() - .frame(width: 40) - } else { - image + .frame(width: 20) + .padding([.leading, .bottom], 10) } } - .frame(maxWidth: .infinity, maxHeight: .infinity) - - if thumbnail.metadata.isVideo { - Image(systemName: "play.fill") - .resizable() - .foregroundColor(Color(uiColor: .systemGray4)) - .scaledToFit() - .frame(width: 20) - .padding([.leading, .bottom], 10) + .overlay { + if isInSelectMode, isTappedInSelectMode { + Color.black.opacity(0.6).frame(maxWidth: .infinity) + } + } + .overlay(alignment: .bottomTrailing) { + if isInSelectMode, isTappedInSelectMode { + Image(systemName: "checkmark.circle.fill") + .resizable() + .foregroundColor(.blue) + .background(.white) + .clipShape(Circle()) + .scaledToFit() + .frame(width: 20) + .padding([.trailing, .bottom], 10) + } + } + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + .background(Color(uiColor: .systemGray6)) + .onTapGesture { + if isInSelectMode { isTappedInSelectMode.toggle() } + onTap(thumbnail, isTappedInSelectMode) + } + .onChange(of: isInSelectMode) { newValue in + isTappedInSelectMode = !newValue } } - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - .background(Color(uiColor: .systemGray6)) - .onTapGesture { - onTap(thumbnail) - } - } -} - -struct NCMediaCell_Previews: PreviewProvider { - static var previews: some View { - let mockMetadata = tableMetadata() - - NCMediaCell(thumbnail: .init(image: UIImage(systemName: "video.fill")!, metadata: mockMetadata), shrinkRatio: 1, onTap: { _ in }) - } } struct NCMediaLoadingCell: View { @@ -88,13 +104,3 @@ struct NCMediaLoadingCell: View { } } } - -struct NCMediaLoadingCell_Previews: PreviewProvider { - static var previews: some View { - let mockMetadata = tableMetadata() - - GeometryReader { proxy in - NCMediaLoadingCell(itemsInRow: 1, metadata: tableMetadata(), geometryProxy: proxy, spacing: 2) - } - } -} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 0411dae1b1..d30e61fbff 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -11,9 +11,35 @@ import PreviewSnapshots import NextcloudKit import VisibilityTrackingScrollView -class NCMediaUIHostingController: UIHostingController { +protocol DataDelegate: AnyObject { + func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) +} + +class NCMediaUIHostingController: UIHostingController, DataDelegate { required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder, rootView: NCMediaNew()) + let view = NCMediaNew() + super.init(coder: aDecoder, rootView: view) + rootView.dataModelDelegate = self + } + + func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) { + if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { + var index = 0 + for medatasImage in metadatas { + if medatasImage.ocId == selectedMetadata.ocId { + viewController.currentIndex = index + break + } + index += 1 + } + viewController.metadatas = metadatas + +// NCViewer.shared.view(viewController: UINavigationController(rootViewController: self), metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) + +// let navController = UINavigationController(rootViewController: self) +// self.navigationController!.pushViewController(viewController, animated: true) + self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) + } } } @@ -21,7 +47,7 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { let metadatas: [tableMetadata] let selectedMetadata: tableMetadata - func makeUIViewController(context: UIViewControllerRepresentableContext) -> UINavigationController { + func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { var index = 0 @@ -34,13 +60,13 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { } viewController.metadatas = metadatas - return UINavigationController(rootViewController: viewController) + return viewController } else { - return UINavigationController() + return NCViewerMediaPage() } } - func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext) {} + func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} } struct NCMediaNew: View { @@ -49,121 +75,144 @@ struct NCMediaNew: View { @State private var title = "Media" @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false - @State private var selectedMetadata = tableMetadata() + @State private var tappedMetadata = tableMetadata() @State private var sort: Int = 0 + @State private var isInSelectMode = false + @State private var selectedMetadataInSelectMode: [tableMetadata] = [] + + weak var dataModelDelegate: DataDelegate? var body: some View { - GeometryReader { outerProxy in - ZStack(alignment: .top) { - VisibilityTrackingScrollView(action: cellVisibilityDidChange) { - LazyVStack(alignment: .leading, spacing: 2) { - ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy) { tappedThumbnail in - selectedMetadata = tappedThumbnail.metadata - isMediaViewControllerPresented = true + NavigationView { + GeometryReader { outerProxy in + ZStack(alignment: .top) { + VisibilityTrackingScrollView(action: cellVisibilityDidChange) { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, tappedInSelectMode in + + if tappedInSelectMode { + selectedMetadataInSelectMode.append(tappedThumbnail.metadata) + } else { + selectedMetadataInSelectMode.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) + } + + print(selectedMetadataInSelectMode) + + if !isInSelectMode { + tappedMetadata = tappedThumbnail.metadata + dataModelDelegate?.updateData(metadatas: vm.metadatas, selectedMetadata: tappedMetadata, image: tappedThumbnail.image) +// isMediaViewControllerPresented = true + } + } } - } - if vm.needsLoadingMoreItems { - ProgressView() - .frame(maxWidth: .infinity) - .onAppear { vm.loadMoreItems() } - .padding(.top, 10) - } - } - .padding(.top, 70) - .padding(.bottom, 40) - .background(GeometryReader { geometry in - Color.clear - .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) - }) - .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in - withAnimation(.easeInOut) { - isScrolledToTop = value.y >= -10 + if vm.needsLoadingMoreItems { + ProgressView() + .frame(maxWidth: .infinity) + .onAppear { vm.loadMoreItems() } + .padding(.top, 10) + } } - } - - } - .refreshable { - vm.onPullToRefresh() - } - .coordinateSpace(name: "scroll") - - HStack(content: { - HStack { - Text(title) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(isScrolledToTop ? Color.primary : .white) - Spacer() - Button(action: {}, label: { - Text("Select").font(.system(size: 14)) - .foregroundStyle(isScrolledToTop ? .blue : .white) + .padding(.top, 70) + .padding(.bottom, 40) + .background(GeometryReader { geometry in + Color.clear + .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) }) - .padding(.horizontal, 6) - .padding(.vertical, 3) - .background(.ultraThinMaterial) - .cornerRadius(.infinity) - Menu { - Section { - Button(action: { - vm.filterClassTypeImage = !vm.filterClassTypeImage - vm.filterClassTypeVideo = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") - }) - Button(action: { - vm.filterClassTypeVideo = !vm.filterClassTypeVideo - vm.filterClassTypeImage = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") - }) - } - - Section { - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") - }) - } - - Picker("Sorting options", selection: $sort) { - Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) - Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) - Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in + withAnimation(.easeInOut) { + isScrolledToTop = value.y >= -10 } - .pickerStyle(.menu) - } label: { - Image(systemName: "ellipsis").font(.system(size: 15)) - .padding(.horizontal, 2) - .padding(.vertical, 8) - .background(.ultraThinMaterial) - .cornerRadius(.infinity) - .foregroundColor(isScrolledToTop ? Color.blue : .white) + } - } } - }) - .frame(maxWidth: .infinity) - .padding([.horizontal, .top], 10) - .padding(.bottom, 20) - .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + .refreshable { + vm.onPullToRefresh() + } + .coordinateSpace(name: "scroll") + + HStack(content: { + HStack { + Text(title) + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(isScrolledToTop ? Color.primary : .white) + Spacer() + Button(action: { + isInSelectMode.toggle() + }, label: { + Text(NSLocalizedString(isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) + .foregroundStyle(isScrolledToTop ? .blue : .white) + }) + .padding(.horizontal, 6) + .padding(.vertical, 3) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) + + if !isInSelectMode { + Menu { + Section { + Button(action: { + vm.filterClassTypeImage = !vm.filterClassTypeImage + vm.filterClassTypeVideo = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") + }) + Button(action: { + vm.filterClassTypeVideo = !vm.filterClassTypeVideo + vm.filterClassTypeImage = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + }) + } + + Section { + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } + + Picker("Sorting options", selection: $sort) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + } + .pickerStyle(.menu) + } label: { + Image(systemName: "ellipsis").font(.system(size: 15)) + .padding(.horizontal, 2) + .padding(.vertical, 8) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) + .foregroundColor(isScrolledToTop ? Color.blue : .white) + + } + } + } + }) + .frame(maxWidth: .infinity) + .padding([.horizontal, .top], 10) + .padding(.bottom, 20) + .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + } } - } - .onRotate { orientation in - if orientation.isLandscapeHardCheck { - columns = 6 - } else { - columns = 2 + .onRotate { orientation in + if orientation.isLandscapeHardCheck { + columns = 6 + } else { + columns = 2 + } } + .onAppear { vm.loadData() } + .fullScreenCover(isPresented: $isMediaViewControllerPresented) { + NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) } - .onAppear { vm.loadData() } - .fullScreenCover(isPresented: $isMediaViewControllerPresented) { - NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: selectedMetadata) } } diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index ee312d7852..c0add6f476 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -13,8 +13,8 @@ import VisibilityTrackingScrollView struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy - let onCellTap: (ScaledThumbnail) -> Void - + @Binding var isInSelectMode: Bool + let onCellTap: (ScaledThumbnail, Bool) -> Void @StateObject private var vm = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -26,7 +26,7 @@ struct NCMediaRow: View { } } else { ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, onTap: onCellTap) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onTap: onCellTap) } } } From 46039bdf023ed75c4a49ff54f73c26b2964f5238 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:01:20 +0200 Subject: [PATCH 063/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 4 ++-- iOSClient/Media/NCMediaNew.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 075291252e..4b118f59f9 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -29,7 +29,7 @@ struct NCMediaCell: View { })) ZStack(alignment: .center) { -// NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { + NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { if thumbnail.isDefaultImage { image .foregroundColor(Color(uiColor: .systemGray4)) @@ -38,7 +38,7 @@ struct NCMediaCell: View { } else { image } -// } + } } .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index d30e61fbff..74b1622045 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -212,8 +212,8 @@ struct NCMediaNew: View { .onAppear { vm.loadData() } .fullScreenCover(isPresented: $isMediaViewControllerPresented) { NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) - } - } + } + }.navigationBarHidden(true) } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { From 8c592b312843c925be0f2e718d2c24a80344921f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:10:10 +0200 Subject: [PATCH 064/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 74b1622045..347426fca2 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -34,11 +34,11 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate } viewController.metadatas = metadatas -// NCViewer.shared.view(viewController: UINavigationController(rootViewController: self), metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) - + NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) + // let navController = UINavigationController(rootViewController: self) // self.navigationController!.pushViewController(viewController, animated: true) - self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) +// self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) } } } @@ -213,7 +213,7 @@ struct NCMediaNew: View { .fullScreenCover(isPresented: $isMediaViewControllerPresented) { NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) } - }.navigationBarHidden(true) + } } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { From 1368edc4a1fa2098f43600256ea11b2b6d2f686f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:10:22 +0200 Subject: [PATCH 065/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 347426fca2..8f38d3163f 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -43,30 +43,30 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate } } -struct NCViewerMediaPageController: UIViewControllerRepresentable { - let metadatas: [tableMetadata] - let selectedMetadata: tableMetadata - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { - - if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { - var index = 0 - for medatasImage in metadatas { - if medatasImage.ocId == selectedMetadata.ocId { - viewController.currentIndex = index - break - } - index += 1 - } - viewController.metadatas = metadatas - - return viewController - } else { - return NCViewerMediaPage() - } - } - - func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} +//struct NCViewerMediaPageController: UIViewControllerRepresentable { +// let metadatas: [tableMetadata] +// let selectedMetadata: tableMetadata +// +// func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { +// +// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { +// var index = 0 +// for medatasImage in metadatas { +// if medatasImage.ocId == selectedMetadata.ocId { +// viewController.currentIndex = index +// break +// } +// index += 1 +// } +// viewController.metadatas = metadatas +// +// return viewController +// } else { +// return NCViewerMediaPage() +// } +// } +// +// func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} } struct NCMediaNew: View { From d4fd42940384a8c51d8de6143eb0833715c3928d Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:10:43 +0200 Subject: [PATCH 066/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 8f38d3163f..ce5ff88b37 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -67,7 +67,7 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate // } // // func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} -} +//} struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() From 27e2563bdd53edb3d612d9374144f1d380425904 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:11:36 +0200 Subject: [PATCH 067/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index ce5ff88b37..347426fca2 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -43,31 +43,31 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate } } -//struct NCViewerMediaPageController: UIViewControllerRepresentable { -// let metadatas: [tableMetadata] -// let selectedMetadata: tableMetadata -// -// func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { -// -// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { -// var index = 0 -// for medatasImage in metadatas { -// if medatasImage.ocId == selectedMetadata.ocId { -// viewController.currentIndex = index -// break -// } -// index += 1 -// } -// viewController.metadatas = metadatas -// -// return viewController -// } else { -// return NCViewerMediaPage() -// } -// } -// -// func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} -//} +struct NCViewerMediaPageController: UIViewControllerRepresentable { + let metadatas: [tableMetadata] + let selectedMetadata: tableMetadata + + func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { + + if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { + var index = 0 + for medatasImage in metadatas { + if medatasImage.ocId == selectedMetadata.ocId { + viewController.currentIndex = index + break + } + index += 1 + } + viewController.metadatas = metadatas + + return viewController + } else { + return NCViewerMediaPage() + } + } + + func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} +} struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() From 783d7c3134ecd1a9c6a12a80b5aad4485497ded6 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 15:47:08 +0200 Subject: [PATCH 068/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 1 + .../NCViewerMediaPage.storyboard | 6 +++--- .../NCViewerMedia/NCViewerMediaPage.swift | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 347426fca2..31bf0e5f58 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -59,6 +59,7 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { index += 1 } viewController.metadatas = metadatas + viewController.hidesBottomBarWhenPushed = true return viewController } else { diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard index ba071d548f..49f376b517 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard @@ -1,9 +1,9 @@ - + - + @@ -12,7 +12,7 @@ - + diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index efb8d07b3d..6f6a0b2bee 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -87,6 +87,25 @@ class NCViewerMediaPage: UIViewController { viewerMediaScreenMode = .normal } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if currentViewController.metadata.isImage { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] + } else { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] + } + + self.tabBarController?.tabBar.isHidden = true + + view.setNeedsLayout() + view.layoutIfNeeded() + } + + override func viewWillDisappear(_ animated: Bool) { + self.tabBarController?.tabBar.isHidden = false + } + override func viewDidLoad() { super.viewDidLoad() From 18ee3109b31da2f5034d605093b4d6ba53903dec Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Sep 2023 16:29:34 +0200 Subject: [PATCH 069/103] Add sorting Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 13 ++++--------- iOSClient/Media/NCMediaViewModel.swift | 14 +++++++++++++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 31bf0e5f58..d0ec58c313 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -35,10 +35,6 @@ class NCMediaUIHostingController: UIHostingController, DataDelegate viewController.metadatas = metadatas NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) - -// let navController = UINavigationController(rootViewController: self) -// self.navigationController!.pushViewController(viewController, animated: true) -// self.present(UINavigationController(rootViewController: viewController), animated: true, completion: nil) } } } @@ -77,7 +73,6 @@ struct NCMediaNew: View { @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false @State private var tappedMetadata = tableMetadata() - @State private var sort: Int = 0 @State private var isInSelectMode = false @State private var selectedMetadataInSelectMode: [tableMetadata] = [] @@ -179,10 +174,10 @@ struct NCMediaNew: View { }) } - Picker("Sorting options", selection: $sort) { - Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(0) - Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(1) - Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(2) + Picker("Sorting options", selection: $vm.sortType) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) } .pickerStyle(.menu) } label: { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 33a5d0e2d2..702f24686e 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -9,6 +9,10 @@ import NextcloudKit import Combine +enum SortType: String { + case modifiedDate = "date", creationDate = "creationDate", uploadDate = "uploadDate" +} + @MainActor class NCMediaViewModel: ObservableObject { @Published var metadatas: [tableMetadata] = [] @@ -22,6 +26,8 @@ import Combine @Published internal var filterClassTypeImage = false @Published internal var filterClassTypeVideo = false + @Published internal var sortType: SortType = SortType(rawValue: CCUtility.getMediaSortDate()) ?? .modifiedDate + private var cancellables: Set = [] internal var needsLoadingMoreItems = true @@ -36,7 +42,13 @@ import Combine searchNewMedia() $filterClassTypeImage.sink { _ in self.loadData() }.store(in: &cancellables) - $filterClassTypeVideo.sink{ _ in self.loadData() }.store(in: &cancellables) + $filterClassTypeVideo.sink { _ in self.loadData() }.store(in: &cancellables) + $sortType.sink { sortType in + print(sortType.rawValue) + CCUtility.setMediaSortDate(sortType.rawValue) + self.loadData() + } + .store(in: &cancellables) } deinit { From 9363466cd4211d9dae49199d3e0a2653c38b5e70 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 19 Sep 2023 15:26:27 +0200 Subject: [PATCH 070/103] move rightBarButtonItems in viewWillAppear --- .../Viewer/NCViewerMedia/NCViewerMediaPage.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 6f6a0b2bee..0881937bc1 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -148,12 +148,6 @@ class NCViewerMediaPage: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil) - - if currentViewController.metadata.isImage { - navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] - } else { - navigationItem.rightBarButtonItems = [moreNavigationItem] - } } deinit { @@ -177,6 +171,16 @@ class NCViewerMediaPage: UIViewController { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil) } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if currentViewController.metadata.isImage { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] + } else { + self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] + } + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) From bd6e384e41e5445f873405adb47e06b9c9287c89 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 19 Sep 2023 16:03:04 +0200 Subject: [PATCH 071/103] Update NCViewerMediaPage.swift --- .../NCViewerMedia/NCViewerMediaPage.swift | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 0881937bc1..99123cfc4d 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -87,25 +87,6 @@ class NCViewerMediaPage: UIViewController { viewerMediaScreenMode = .normal } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if currentViewController.metadata.isImage { - self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] - } else { - self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] - } - - self.tabBarController?.tabBar.isHidden = true - - view.setNeedsLayout() - view.layoutIfNeeded() - } - - override func viewWillDisappear(_ animated: Bool) { - self.tabBarController?.tabBar.isHidden = false - } - override func viewDidLoad() { super.viewDidLoad() @@ -174,11 +155,15 @@ class NCViewerMediaPage: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + guard let navigationController = self.navigationController else { return } + if currentViewController.metadata.isImage { self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] } else { self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] } + + self.tabBarController?.tabBar.isHidden = true } override func viewDidAppear(_ animated: Bool) { @@ -195,6 +180,12 @@ class NCViewerMediaPage: UIViewController { clearCommandCenter() } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + self.tabBarController?.tabBar.isHidden = false + } + override var preferredStatusBarStyle: UIStatusBarStyle { if viewerMediaScreenMode == .normal { From 62262c2d9d4aa44308987de246bafc855101dc94 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 13:30:27 +0200 Subject: [PATCH 072/103] Fix toolbar icons, add trash icon Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 16 +++ iOSClient/Extensions/Binding+Extension.swift | 16 +++ iOSClient/Media/Cell/NCMediaCell.swift | 17 +-- iOSClient/Media/NCMediaNew.swift | 131 +++++++++++-------- iOSClient/Media/NCMediaRow.swift | 4 +- 5 files changed, 118 insertions(+), 66 deletions(-) create mode 100644 iOSClient/Extensions/Binding+Extension.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8a5ff22f5b..58fefd9500 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -84,6 +84,13 @@ F30A6BF62AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A6BF72AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A6BF82AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; + F30A6BFA2ABAE84900148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFB2ABAE84900148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFC2ABAE84900148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFD2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -848,6 +855,7 @@ C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesIntegrationTests.swift; sourceTree = ""; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceKeys.swift; sourceTree = ""; }; + F30A6BF92ABAE84900148857 /* Binding+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Extension.swift"; sourceTree = ""; }; F30A96042A27299D00D7BCFE /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = EnvVars.stencil; sourceTree = ""; }; F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EnvVars.generated.swift; path = Sourcery/EnvVars.generated.swift; sourceTree = ""; }; @@ -2154,6 +2162,7 @@ F77BB745289984CA0090FC19 /* UIViewController+Extension.swift */, F7E8A390295DC5E0006CB2D0 /* View+Extension.swift */, F7EE66AC2A20B226009AE765 /* UILabel+Extension.swift */, + F30A6BF92ABAE84900148857 /* Binding+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -3381,6 +3390,7 @@ F79B646326CA661600838ACA /* UIControl+Extension.swift in Sources */, F78A10C429322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */, F75CA1482962F13700B01130 /* HUDView.swift in Sources */, + F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */, AF4BF61C27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, AF817EF4274BC781009ED85B /* NCUserBaseUrl.swift in Sources */, F78E2D6B29AF02DB0024D4F3 /* Database.swift in Sources */, @@ -3464,6 +3474,7 @@ F7490E7529882BE2009DCE94 /* NCManageDatabase+Directory.swift in Sources */, F7864AD12A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */, F7490E8729882CA8009DCE94 /* ThreadSafeDictionary.swift in Sources */, + F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */, F757CC8729E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, F7490E8229882C80009DCE94 /* NCManageDatabase+E2EE.swift in Sources */, F7490E7829882C28009DCE94 /* NCUtility.swift in Sources */, @@ -3537,6 +3548,7 @@ AF22B217277D196700DAB0CC /* NCShareExtension+DataSource.swift in Sources */, F76D364728A4F8BF00214537 /* NCActivityIndicator.swift in Sources */, F749B654297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, + F30A6BFD2ABAE84A00148857 /* Binding+Extension.swift in Sources */, F780710A1EDAB65800EAFFF6 /* NSNotificationCenter+MainThread.m in Sources */, F79EC77F26316193004E59D6 /* NCRenameFile.swift in Sources */, AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */, @@ -3582,6 +3594,7 @@ F793E59D28B761E7005E4B02 /* NCNetworking.swift in Sources */, F7BF9D832934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */, F757CC8329E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */, + F30A6BFB2ABAE84900148857 /* Binding+Extension.swift in Sources */, F74B6D962A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */, F749B652297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, F783030628B4C51E00B84583 /* String+Extension.swift in Sources */, @@ -3625,6 +3638,7 @@ F771E3D320E2392D00AFB62D /* FileProviderExtension.swift in Sources */, F771E3D520E2392D00AFB62D /* FileProviderItem.swift in Sources */, AF4BF616275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */, + F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */, F343A4B72A1E084300DDA874 /* PHAsset+Extension.swift in Sources */, F7434B3620E23FE000417916 /* NCManageDatabase.swift in Sources */, F798F0E725880609000DAFFD /* UIColor+Extension.swift in Sources */, @@ -3759,6 +3773,7 @@ F74B6D952A7E239A00F03C5F /* NCManageDatabase+Chunk.swift in Sources */, F702F2F725EE5CED008F8E80 /* NCLogin.swift in Sources */, F7E98C1627E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */, + F30A6BFA2ABAE84900148857 /* Binding+Extension.swift in Sources */, F7F4F11227ECDC52008676F9 /* UIFont+Extension.swift in Sources */, AF93471A27E2361E002537EE /* NCShareAdvancePermissionHeader.swift in Sources */, F7F878AE1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */, @@ -3907,6 +3922,7 @@ F78E2D6729AF02DB0024D4F3 /* Database.swift in Sources */, F7A8D73628F17E1A008BBE1C /* NCManageDatabase+Activity.swift in Sources */, F7A8D73E28F181E2008BBE1C /* NCUserBaseUrl.swift in Sources */, + F30A6BFC2ABAE84900148857 /* Binding+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOSClient/Extensions/Binding+Extension.swift b/iOSClient/Extensions/Binding+Extension.swift new file mode 100644 index 0000000000..4fed3583fc --- /dev/null +++ b/iOSClient/Extensions/Binding+Extension.swift @@ -0,0 +1,16 @@ +// +// Binding+Extension.swift +// Nextcloud +// +// Created by Milen on 20.09.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI + +prefix func ! (value: Binding) -> Binding { + Binding( + get: { !value.wrappedValue }, + set: { value.wrappedValue = !$0 } + ) +} diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 4b118f59f9..cc195606a1 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -15,8 +15,8 @@ struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat @Binding var isInSelectMode: Bool - let onTap: (ScaledThumbnail, Bool) -> Void - @State private var isTappedInSelectMode = false + let onSelected: (ScaledThumbnail, Bool) -> Void + @State private var isSelected = false var body: some View { let image = Image(uiImage: thumbnail.image) @@ -38,7 +38,7 @@ struct NCMediaCell: View { } else { image } - } + }.disabled(isInSelectMode) } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -53,12 +53,12 @@ struct NCMediaCell: View { } } .overlay { - if isInSelectMode, isTappedInSelectMode { + if isInSelectMode, isSelected { Color.black.opacity(0.6).frame(maxWidth: .infinity) } } .overlay(alignment: .bottomTrailing) { - if isInSelectMode, isTappedInSelectMode { + if isInSelectMode, isSelected { Image(systemName: "checkmark.circle.fill") .resizable() .foregroundColor(.blue) @@ -72,11 +72,12 @@ struct NCMediaCell: View { .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) .background(Color(uiColor: .systemGray6)) .onTapGesture { - if isInSelectMode { isTappedInSelectMode.toggle() } - onTap(thumbnail, isTappedInSelectMode) + if isInSelectMode { isSelected.toggle() } + onSelected(thumbnail, isSelected) } .onChange(of: isInSelectMode) { newValue in - isTappedInSelectMode = !newValue + isSelected = !newValue + onSelected(thumbnail, isSelected) } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index d0ec58c313..5eddbc2ce2 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -76,29 +76,28 @@ struct NCMediaNew: View { @State private var isInSelectMode = false @State private var selectedMetadataInSelectMode: [tableMetadata] = [] + @State var titleColor = Color.primary + @State var toolbarItemsColor = Color.blue + @State var toolbarColors = [Color.clear] + weak var dataModelDelegate: DataDelegate? var body: some View { - NavigationView { GeometryReader { outerProxy in + NavigationView { ZStack(alignment: .top) { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, tappedInSelectMode in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, isSelected in - if tappedInSelectMode { + // TODO: Only do selection here + if isSelected { selectedMetadataInSelectMode.append(tappedThumbnail.metadata) + print(selectedMetadataInSelectMode) } else { selectedMetadataInSelectMode.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) - } - - print(selectedMetadataInSelectMode) - - if !isInSelectMode { - tappedMetadata = tappedThumbnail.metadata - dataModelDelegate?.updateData(metadatas: vm.metadatas, selectedMetadata: tappedMetadata, image: tappedThumbnail.image) -// isMediaViewControllerPresented = true + print(selectedMetadataInSelectMode) } } } @@ -117,8 +116,12 @@ struct NCMediaNew: View { .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) }) .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in - withAnimation(.easeInOut) { - isScrolledToTop = value.y >= -10 + let isScrolledToTop = value.y >= -10 + + withAnimation(.default) { + titleColor = isScrolledToTop ? Color.primary : .white + toolbarItemsColor = isScrolledToTop ? .blue : .white + toolbarColors = isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)] } } @@ -128,74 +131,74 @@ struct NCMediaNew: View { } .coordinateSpace(name: "scroll") + // Toolbar + HStack(content: { HStack { Text(title) .font(.system(size: 20, weight: .bold)) - .foregroundStyle(isScrolledToTop ? Color.primary : .white) + .foregroundStyle(titleColor) + Spacer() + Button(action: { isInSelectMode.toggle() }, label: { Text(NSLocalizedString(isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) - .foregroundStyle(isScrolledToTop ? .blue : .white) + .foregroundStyle(toolbarItemsColor) }) .padding(.horizontal, 6) .padding(.vertical, 3) .background(.ultraThinMaterial) .cornerRadius(.infinity) - if !isInSelectMode { - Menu { - Section { - Button(action: { - vm.filterClassTypeImage = !vm.filterClassTypeImage - vm.filterClassTypeVideo = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") - }) - Button(action: { - vm.filterClassTypeVideo = !vm.filterClassTypeVideo - vm.filterClassTypeImage = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") - }) - } + if isInSelectMode, !selectedMetadataInSelectMode.isEmpty { + ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) + } - Section { - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") - }) - } + Menu { + Section { + Button(action: { + vm.filterClassTypeImage = !vm.filterClassTypeImage + vm.filterClassTypeVideo = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") + }) + Button(action: { + vm.filterClassTypeVideo = !vm.filterClassTypeVideo + vm.filterClassTypeImage = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + }) + } - Picker("Sorting options", selection: $vm.sortType) { - Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) - Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) - Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) - } - .pickerStyle(.menu) - } label: { - Image(systemName: "ellipsis").font(.system(size: 15)) - .padding(.horizontal, 2) - .padding(.vertical, 8) - .background(.ultraThinMaterial) - .cornerRadius(.infinity) - .foregroundColor(isScrolledToTop ? Color.blue : .white) + Section { + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } + Picker("Sorting options", selection: $vm.sortType) { + Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) + Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) + Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) } + .pickerStyle(.menu) + } label: { + ToolbarCircularButton(imageSystemName: "ellipsis", toolbarItemsColor: $toolbarItemsColor) } } }) .frame(maxWidth: .infinity) .padding([.horizontal, .top], 10) .padding(.bottom, 20) - .background(LinearGradient(gradient: Gradient(colors: isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)]), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) + .background(LinearGradient(gradient: Gradient(colors: toolbarColors), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } } .onRotate { orientation in @@ -221,6 +224,22 @@ struct NCMediaNew: View { } } +struct ToolbarCircularButton: View { + let imageSystemName: String + @Binding var toolbarItemsColor: Color + + var body: some View { + Image(systemName: imageSystemName) + .resizable() + .scaledToFit() + .frame(width: 13, height: 12) + .padding(5) + .background(.ultraThinMaterial) + .clipShape(Circle()) + .foregroundColor(toolbarItemsColor) + } +} + struct NCMediaNew_Previews: PreviewProvider { static var previews: some View { snapshots.previews.previewLayout(.sizeThatFits) diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index c0add6f476..8b72a9e88f 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -14,7 +14,7 @@ struct NCMediaRow: View { let metadatas: [tableMetadata] let geometryProxy: GeometryProxy @Binding var isInSelectMode: Bool - let onCellTap: (ScaledThumbnail, Bool) -> Void + let onCellSelected: (ScaledThumbnail, Bool) -> Void @StateObject private var vm = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -26,7 +26,7 @@ struct NCMediaRow: View { } } else { ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onTap: onCellTap) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onSelected: onCellSelected) } } } From 44eb2a3f42b135671ed36753e1f9a49e9a27c425 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 14:43:55 +0200 Subject: [PATCH 073/103] Delete photos support Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 22 ++++++------- iOSClient/Media/NCMediaNew.swift | 34 +++++++++++++------- iOSClient/Media/NCMediaViewModel.swift | 43 ++++++++++++++++---------- 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 9f01ab842f..2b30876cf9 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -76,7 +76,6 @@ C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; - F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6BEF2AB4AAB700148857 /* Refreshable */; }; F30A6BF22AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A6BF32AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A6BF42AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; @@ -91,6 +90,7 @@ F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6C032ABB1BDE00148857 /* JGProgressHUD-SwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C022ABB1BDE00148857 /* JGProgressHUD-SwiftUI */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -1519,10 +1519,10 @@ F3EFBF5F2AB2100E00B5724C /* Shimmer in Frameworks */, F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */, F76DA963277B760E0082465B /* Queuer in Frameworks */, - F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */, F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, F70B86752642CE3B00ED5349 /* FirebaseCrashlytics in Frameworks */, F7A1050E29E587AF00FFD92B /* TagListView in Frameworks */, + F30A6C032ABB1BDE00148857 /* JGProgressHUD-SwiftUI in Frameworks */, F76DA969277B77EA0082465B /* DropDown in Frameworks */, F7F623B52A5EF4D30022D3D4 /* Gzip in Frameworks */, F75EAED826D2552E00F4320E /* MarqueeLabel in Frameworks */, @@ -2923,7 +2923,7 @@ F7F623B42A5EF4D30022D3D4 /* Gzip */, F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, F3EFBF5E2AB2100E00B5724C /* Shimmer */, - F30A6BEF2AB4AAB700148857 /* Refreshable */, + F30A6C022ABB1BDE00148857 /* JGProgressHUD-SwiftUI */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3101,7 +3101,7 @@ F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, - F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */, + F30A6C012ABB1BDE00148857 /* XCRemoteSwiftPackageReference "JGProgressHUD-SwiftUI" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4940,12 +4940,12 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */ = { + F30A6C012ABB1BDE00148857 /* XCRemoteSwiftPackageReference "JGProgressHUD-SwiftUI" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/c-villain/Refreshable"; + repositoryURL = "https://github.com/JonasGessner/JGProgressHUD-SwiftUI.git"; requirement = { - kind = exactVersion; - version = 0.2.0; + kind = upToNextMajorVersion; + minimumVersion = 0.1.1; }; }; F31F694B2A2F6EFA00162F76 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { @@ -5195,10 +5195,10 @@ package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; productName = UICKeyChainStore; }; - F30A6BEF2AB4AAB700148857 /* Refreshable */ = { + F30A6C022ABB1BDE00148857 /* JGProgressHUD-SwiftUI */ = { isa = XCSwiftPackageProductDependency; - package = F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */; - productName = Refreshable; + package = F30A6C012ABB1BDE00148857 /* XCRemoteSwiftPackageReference "JGProgressHUD-SwiftUI" */; + productName = "JGProgressHUD-SwiftUI"; }; F30A96332A2DFCD000D7BCFE /* Realm */ = { isa = XCSwiftPackageProductDependency; diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 5eddbc2ce2..3ffa8579c3 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -10,6 +10,7 @@ import SwiftUI import PreviewSnapshots import NextcloudKit import VisibilityTrackingScrollView +import JGProgressHUD_SwiftUI protocol DataDelegate: AnyObject { func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) @@ -74,30 +75,30 @@ struct NCMediaNew: View { @State private var isMediaViewControllerPresented = false @State private var tappedMetadata = tableMetadata() @State private var isInSelectMode = false - @State private var selectedMetadataInSelectMode: [tableMetadata] = [] - @State var titleColor = Color.primary - @State var toolbarItemsColor = Color.blue - @State var toolbarColors = [Color.clear] + @State private var titleColor = Color.primary + @State private var toolbarItemsColor = Color.blue + @State private var toolbarColors = [Color.clear] + + @State private var showDeleteConfirmation = false weak var dataModelDelegate: DataDelegate? + @EnvironmentObject var hudCoordinator: JGProgressHUDCoordinator + var body: some View { - GeometryReader { outerProxy in - NavigationView { + GeometryReader { outerProxy in + NavigationView { ZStack(alignment: .top) { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, isSelected in - // TODO: Only do selection here if isSelected { - selectedMetadataInSelectMode.append(tappedThumbnail.metadata) - print(selectedMetadataInSelectMode) + vm.selectedMetadatas.append(tappedThumbnail.metadata) } else { - selectedMetadataInSelectMode.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) - print(selectedMetadataInSelectMode) + vm.selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) } } } @@ -152,8 +153,17 @@ struct NCMediaNew: View { .background(.ultraThinMaterial) .cornerRadius(.infinity) - if isInSelectMode, !selectedMetadataInSelectMode.isEmpty { + if isInSelectMode, !vm.selectedMetadatas.isEmpty { ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) + .onTapGesture { + showDeleteConfirmation = true + } + .confirmationDialog("", isPresented: $showDeleteConfirmation) { + Button("Delete selected media", role: .destructive) { + vm.deleteSelectedMetadata() + isInSelectMode = false + } + } } Menu { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 702f24686e..4aa3ad9649 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -15,7 +15,8 @@ enum SortType: String { @MainActor class NCMediaViewModel: ObservableObject { @Published var metadatas: [tableMetadata] = [] - + @Published var selectedMetadatas: [tableMetadata] = [] + private var account: String = "" private var lastContentOffsetY: CGFloat = 0 private var mediaPath = "" @@ -121,6 +122,26 @@ enum SortType: String { func onCellTapped(metadata: tableMetadata) { appDelegate?.activeServerUrl = metadata.serverUrl } + + func deleteSelectedMetadata() { + let notLocked = selectedMetadatas.allSatisfy { !$0.lock } + + if notLocked { + Task { + var error = NKError() + var ocId: [String] = [] + for metadata in selectedMetadatas where error == .success { + error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false) + if error == .success { + ocId.append(metadata.ocId) + } + } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "onlyLocalCache": false, "error": error]) + } +// completion?() + + } + } } // MARK: Notifications @@ -129,21 +150,11 @@ extension NCMediaViewModel { @objc func deleteFile(_ notification: NSNotification) { guard let userInfo = notification.userInfo as NSDictionary?, let error = userInfo["error"] as? NKError else { return } - let onlyLocalCache: Bool = userInfo["onlyLocalCache"] as? Bool ?? false - self.queryDB(isForced: true) + loadData() - if error == .success, let indexPath = userInfo["indexPath"] as? [IndexPath], !indexPath.isEmpty, !onlyLocalCache { - // collectionView?.performBatchUpdates({ - // collectionView?.deleteItems(at: indexPath) - // }, completion: { _ in - // self.collectionView?.reloadData() - // }) - } else { - if error != .success { - NCContentPresenter.shared.showError(error: error) - } - // self.collectionView?.reloadData() + if error != .success { + NCContentPresenter.shared.showError(error: error) } // if let hud = userInfo["hud"] as? JGProgressHUD { @@ -154,9 +165,7 @@ extension NCMediaViewModel { @objc func moveFile(_ notification: NSNotification) { guard let userInfo = notification.userInfo as NSDictionary? else { return } - // if let hud = userInfo["hud"] as? JGProgressHUD { - // hud.dismiss() - // } + loadData() } @objc func copyFile(_ notification: NSNotification) { From 111afc710ab3ffd152f1617e0c6448440fd5c695 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 14:44:53 +0200 Subject: [PATCH 074/103] Remove lib Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 2bc946424e..e6c4c9de33 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -90,7 +90,6 @@ F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; - F30A6C032ABB1BDE00148857 /* JGProgressHUD-SwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C022ABB1BDE00148857 /* JGProgressHUD-SwiftUI */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -1527,7 +1526,6 @@ F72AD70D28C24B93006CB92D /* NextcloudKit in Frameworks */, F70B86752642CE3B00ED5349 /* FirebaseCrashlytics in Frameworks */, F7A1050E29E587AF00FFD92B /* TagListView in Frameworks */, - F30A6C032ABB1BDE00148857 /* JGProgressHUD-SwiftUI in Frameworks */, F76DA969277B77EA0082465B /* DropDown in Frameworks */, F7F623B52A5EF4D30022D3D4 /* Gzip in Frameworks */, F75EAED826D2552E00F4320E /* MarqueeLabel in Frameworks */, @@ -2930,7 +2928,6 @@ F7F623B42A5EF4D30022D3D4 /* Gzip */, F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, F3EFBF5E2AB2100E00B5724C /* Shimmer */, - F30A6C022ABB1BDE00148857 /* JGProgressHUD-SwiftUI */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3108,7 +3105,6 @@ F7F623B32A5EF4D30022D3D4 /* XCRemoteSwiftPackageReference "GzipSwift" */, F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, - F30A6C012ABB1BDE00148857 /* XCRemoteSwiftPackageReference "JGProgressHUD-SwiftUI" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4949,14 +4945,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - F30A6C012ABB1BDE00148857 /* XCRemoteSwiftPackageReference "JGProgressHUD-SwiftUI" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/JonasGessner/JGProgressHUD-SwiftUI.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.1.1; - }; - }; F31F694B2A2F6EFA00162F76 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; @@ -5204,11 +5192,6 @@ package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; productName = UICKeyChainStore; }; - F30A6C022ABB1BDE00148857 /* JGProgressHUD-SwiftUI */ = { - isa = XCSwiftPackageProductDependency; - package = F30A6C012ABB1BDE00148857 /* XCRemoteSwiftPackageReference "JGProgressHUD-SwiftUI" */; - productName = "JGProgressHUD-SwiftUI"; - }; F30A96332A2DFCD000D7BCFE /* Realm */ = { isa = XCSwiftPackageProductDependency; package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; From 487a194c98fe4f0e832fac64224cf25cf773a785 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 15:24:03 +0200 Subject: [PATCH 075/103] cleaning --- iOSClient/Media/Cell/NCMediaCell.swift | 1 - iOSClient/Media/NCMediaNew.swift | 2 -- .../Viewer/NCViewerMedia/NCViewerMediaPage.swift | 12 +++++------- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index cc195606a1..de98970081 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -39,7 +39,6 @@ struct NCMediaCell: View { image } }.disabled(isInSelectMode) - } .frame(maxWidth: .infinity, maxHeight: .infinity) .overlay(alignment: .bottomLeading) { diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 5eddbc2ce2..6b6beaf616 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -55,8 +55,6 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { index += 1 } viewController.metadatas = metadatas - viewController.hidesBottomBarWhenPushed = true - return viewController } else { return NCViewerMediaPage() diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 99123cfc4d..d3553948db 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -158,12 +158,12 @@ class NCViewerMediaPage: UIViewController { guard let navigationController = self.navigationController else { return } if currentViewController.metadata.isImage { - self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] + navigationController.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] } else { - self.navigationController?.navigationItem.rightBarButtonItems = [moreNavigationItem] + navigationController.navigationItem.rightBarButtonItems = [moreNavigationItem] } - self.tabBarController?.tabBar.isHidden = true + tabBarController?.tabBar.isHidden = true } override func viewDidAppear(_ animated: Bool) { @@ -180,10 +180,8 @@ class NCViewerMediaPage: UIViewController { clearCommandCenter() } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - self.tabBarController?.tabBar.isHidden = false + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() } override var preferredStatusBarStyle: UIStatusBarStyle { From b96101ca82e4131323f0dd25e78517ccf325058b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 15:24:19 +0200 Subject: [PATCH 076/103] Update NCViewerMediaPage.storyboard --- .../NCViewerMediaPage.storyboard | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard index 49f376b517..d79b21c860 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard @@ -3,7 +3,7 @@ - + @@ -12,7 +12,7 @@ - + @@ -566,34 +566,34 @@ - + - + - + - + - + - + - + - + - + From 297f668c7d1656e5d457f2b01acdf9a8f85b190d Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 16:00:55 +0200 Subject: [PATCH 077/103] revert --- .../NCViewerMediaPage.storyboard | 1 + .../NCViewerMedia/NCViewerMediaPage.swift | 26 +++++-------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard index d79b21c860..8a3b8c37a0 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard @@ -49,6 +49,7 @@ + diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index d3553948db..7ec15b5c5d 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -71,8 +71,6 @@ class NCViewerMediaPage: UIViewController { private lazy var moreNavigationItem = UIBarButtonItem(image: UIImage(named: "more")!.image(color: .label, size: 25), style: .plain, target: self, action: #selector(openMenuMore)) private lazy var imageDetailNavigationItem = UIBarButtonItem(image: UIImage(systemName: "info.circle")!.image(color: .label, size: 22), style: .plain, target: self, action: #selector(toggleDetail)) - var dismissAction: (() -> Void)? - // MARK: - View Life Cycle override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { @@ -129,6 +127,12 @@ class NCViewerMediaPage: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil) + + if currentViewController.metadata.isImage { + navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] + } else { + navigationItem.rightBarButtonItems = [moreNavigationItem] + } } deinit { @@ -152,20 +156,6 @@ class NCViewerMediaPage: UIViewController { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil) } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - guard let navigationController = self.navigationController else { return } - - if currentViewController.metadata.isImage { - navigationController.navigationItem.rightBarButtonItems = [moreNavigationItem, imageDetailNavigationItem] - } else { - navigationController.navigationItem.rightBarButtonItems = [moreNavigationItem] - } - - tabBarController?.tabBar.isHidden = true - } - override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -180,10 +170,6 @@ class NCViewerMediaPage: UIViewController { clearCommandCenter() } - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - } - override var preferredStatusBarStyle: UIStatusBarStyle { if viewerMediaScreenMode == .normal { From 656b9b2e50ccddb5fabf5d48761c20777d86c9c5 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 16:03:25 +0200 Subject: [PATCH 078/103] Update NCViewerMediaPage.storyboard --- .../NCViewerMediaPage.storyboard | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard index 8a3b8c37a0..ba071d548f 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.storyboard @@ -1,9 +1,9 @@ - + - + @@ -49,7 +49,6 @@ - @@ -567,34 +566,34 @@ - + - + - + - + - + - + - + - + - + From 3dabb147fe80f4bdcb4d328ae5d593f94f5eed77 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 16:18:43 +0200 Subject: [PATCH 079/103] Update Main.storyboard --- iOSClient/Main/Main.storyboard | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index 557c9918c1..baaa62b8c9 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -3,7 +3,7 @@ - + @@ -24,13 +24,13 @@ - + - + @@ -70,6 +70,25 @@ + + + + + + + + + + + + + + + + + + + @@ -234,15 +253,15 @@ - - + + - - + + - + - + From 2d0d7cebce2b5b2ddfdd90115bc4115a961eca73 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 16:32:47 +0200 Subject: [PATCH 080/103] Update Main.storyboard --- iOSClient/Main/Main.storyboard | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index baaa62b8c9..f6ed5e212b 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -1,12 +1,22 @@ - + - + + + + + + + + + + + @@ -82,7 +92,7 @@ - + @@ -253,16 +263,6 @@ - - - - - - - - - - From cbbb94650f42d11d8bed06832db4d745b8e85997 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 16:39:05 +0200 Subject: [PATCH 081/103] new --- Nextcloud.xcodeproj/project.pbxproj | 4 ++++ iOSClient/Main/Main.storyboard | 8 ++++---- iOSClient/Media/NCMediaUI.storyboard | 27 +++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 iOSClient/Media/NCMediaUI.storyboard diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 58fefd9500..c0d856da3c 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -347,6 +347,7 @@ F7501C322212E57500FB1415 /* NCMedia.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7501C302212E57400FB1415 /* NCMedia.storyboard */; }; F7501C332212E57500FB1415 /* NCMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7501C312212E57400FB1415 /* NCMedia.swift */; }; F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */ = {isa = PBXBuildFile; productRef = F753BA92281FD8020015BFB6 /* EasyTipView */; }; + F7556ED62ABB3A5F00350BD4 /* NCMediaUI.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */; }; F755BD9B20594AC7008C5FBB /* NCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F755BD9A20594AC7008C5FBB /* NCService.swift */; }; F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; }; F757CC8329E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; }; @@ -1051,6 +1052,7 @@ F753701822723D620041C76C /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = ""; }; F753701922723E0D0041C76C /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; F753701A22723EC80041C76C /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCMediaUI.storyboard; sourceTree = ""; }; F755BD9A20594AC7008C5FBB /* NCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCService.swift; sourceTree = ""; }; F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Groupfolders.swift"; sourceTree = ""; }; F757CC8A29E82D0500F31428 /* NCGroupfolders.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCGroupfolders.storyboard; sourceTree = ""; }; @@ -2410,6 +2412,7 @@ children = ( F720B5B72507B9A5008C94E5 /* Cell */, F7501C302212E57400FB1415 /* NCMedia.storyboard */, + F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, F7501C312212E57400FB1415 /* NCMedia.swift */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, @@ -3222,6 +3225,7 @@ files = ( F7362A1F220C853A005101B5 /* LaunchScreen.storyboard in Resources */, F77444F622281649000D5EB0 /* NCGridMediaCell.xib in Resources */, + F7556ED62ABB3A5F00350BD4 /* NCMediaUI.storyboard in Resources */, F78ACD4421903CF20088454D /* NCListCell.xib in Resources */, F3BB464D2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib in Resources */, F7F4F10727ECDBDB008676F9 /* Inconsolata-Black.ttf in Resources */, diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index f6ed5e212b..c6b9e5bf00 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -1,16 +1,16 @@ - + - + - + - + diff --git a/iOSClient/Media/NCMediaUI.storyboard b/iOSClient/Media/NCMediaUI.storyboard new file mode 100644 index 0000000000..7bbcd2b32a --- /dev/null +++ b/iOSClient/Media/NCMediaUI.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + From f0730ed20e48207d40965057e837152995c6ebdc Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 16:49:10 +0200 Subject: [PATCH 082/103] test --- Nextcloud.xcodeproj/project.pbxproj | 4 +++ iOSClient/Media/NCMediaUI.storyboard | 4 +-- iOSClient/Media/NCMediaUI.swift | 46 ++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 iOSClient/Media/NCMediaUI.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index c0d856da3c..bc997b712f 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -348,6 +348,7 @@ F7501C332212E57500FB1415 /* NCMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7501C312212E57400FB1415 /* NCMedia.swift */; }; F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */ = {isa = PBXBuildFile; productRef = F753BA92281FD8020015BFB6 /* EasyTipView */; }; F7556ED62ABB3A5F00350BD4 /* NCMediaUI.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */; }; + F7556ED82ABB3C2100350BD4 /* NCMediaUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7556ED72ABB3C2100350BD4 /* NCMediaUI.swift */; }; F755BD9B20594AC7008C5FBB /* NCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F755BD9A20594AC7008C5FBB /* NCService.swift */; }; F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; }; F757CC8329E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; }; @@ -1053,6 +1054,7 @@ F753701922723E0D0041C76C /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; F753701A22723EC80041C76C /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCMediaUI.storyboard; sourceTree = ""; }; + F7556ED72ABB3C2100350BD4 /* NCMediaUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaUI.swift; sourceTree = ""; }; F755BD9A20594AC7008C5FBB /* NCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCService.swift; sourceTree = ""; }; F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Groupfolders.swift"; sourceTree = ""; }; F757CC8A29E82D0500F31428 /* NCGroupfolders.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCGroupfolders.storyboard; sourceTree = ""; }; @@ -2413,6 +2415,7 @@ F720B5B72507B9A5008C94E5 /* Cell */, F7501C302212E57400FB1415 /* NCMedia.storyboard */, F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */, + F7556ED72ABB3C2100350BD4 /* NCMediaUI.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, F7501C312212E57400FB1415 /* NCMedia.swift */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, @@ -3877,6 +3880,7 @@ F79FFB262A97C24A0055EEA4 /* NCNetworkingE2EEMarkFolder.swift in Sources */, F70D8D8124A4A9BF000A5756 /* NCNetworkingProcessUpload.swift in Sources */, F7D96FCC246ED7E200536D73 /* NCNetworkingCheckRemoteUser.swift in Sources */, + F7556ED82ABB3C2100350BD4 /* NCMediaUI.swift in Sources */, F7E4D9C422ED929B003675FD /* NCShareCommentsCell.swift in Sources */, F717402E24F699A5000C87D5 /* NCFavorite.swift in Sources */, AF2D7C7E2742559100ADF566 /* NCShareUserCell.swift in Sources */, diff --git a/iOSClient/Media/NCMediaUI.storyboard b/iOSClient/Media/NCMediaUI.storyboard index 7bbcd2b32a..99c08b5744 100644 --- a/iOSClient/Media/NCMediaUI.storyboard +++ b/iOSClient/Media/NCMediaUI.storyboard @@ -8,10 +8,10 @@ - + - + diff --git a/iOSClient/Media/NCMediaUI.swift b/iOSClient/Media/NCMediaUI.swift new file mode 100644 index 0000000000..e09a4cfb23 --- /dev/null +++ b/iOSClient/Media/NCMediaUI.swift @@ -0,0 +1,46 @@ +// +// NCMediaUI.swift +// Nextcloud +// +// Created by Marino Faggiana on 20/09/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import Foundation +import SwiftUI + +class NCMediaUI: UIViewController, ObservableObject { + + override func viewDidLoad() { + super.viewDidLoad() + addView() + } + + func addView() { + let testView = SwiftUIView() + let controller = UIHostingController(rootView: testView.environmentObject(self)) + addChild(controller) + controller.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(controller.view) + controller.didMove(toParent: self) + + NSLayoutConstraint.activate([ + controller.view.widthAnchor.constraint(equalTo: view.widthAnchor), + controller.view.heightAnchor.constraint(equalTo: view.heightAnchor), + controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor), + controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + } +} + +struct SwiftUIView: View { + @EnvironmentObject var parent: NCMediaUI + var body: some View { + Button { + // push new view controller + // parent.navigationController?.pushViewController(<#T##viewController: UIViewController##UIViewController#>, animated: <#T##Bool#>) + } label: { + Text("go") + } + } +} From 2060365781ebc3bad78870f22352b18eb5a93893 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 20 Sep 2023 16:52:00 +0200 Subject: [PATCH 083/103] test --- iOSClient/Media/NCMediaNew.swift | 1 + iOSClient/Media/NCMediaUI.swift | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 6b6beaf616..7c5abd27d0 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -66,6 +66,7 @@ struct NCViewerMediaPageController: UIViewControllerRepresentable { struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() + @EnvironmentObject var parent: NCMediaUI @State private var columns = 2 @State private var title = "Media" @State private var isScrolledToTop = true diff --git a/iOSClient/Media/NCMediaUI.swift b/iOSClient/Media/NCMediaUI.swift index e09a4cfb23..d4e4a09108 100644 --- a/iOSClient/Media/NCMediaUI.swift +++ b/iOSClient/Media/NCMediaUI.swift @@ -17,7 +17,7 @@ class NCMediaUI: UIViewController, ObservableObject { } func addView() { - let testView = SwiftUIView() + let testView = NCMediaNew() let controller = UIHostingController(rootView: testView.environmentObject(self)) addChild(controller) controller.view.translatesAutoresizingMaskIntoConstraints = false @@ -33,6 +33,7 @@ class NCMediaUI: UIViewController, ObservableObject { } } +/* struct SwiftUIView: View { @EnvironmentObject var parent: NCMediaUI var body: some View { @@ -44,3 +45,4 @@ struct SwiftUIView: View { } } } +*/ From aa67d2b166474f25af16340b7d58d314597eb2cc Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 16:58:47 +0200 Subject: [PATCH 084/103] Fix build Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 3ffa8579c3..94784b3a17 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -10,7 +10,6 @@ import SwiftUI import PreviewSnapshots import NextcloudKit import VisibilityTrackingScrollView -import JGProgressHUD_SwiftUI protocol DataDelegate: AnyObject { func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) @@ -73,7 +72,6 @@ struct NCMediaNew: View { @State private var title = "Media" @State private var isScrolledToTop = true @State private var isMediaViewControllerPresented = false - @State private var tappedMetadata = tableMetadata() @State private var isInSelectMode = false @State private var titleColor = Color.primary @@ -84,8 +82,6 @@ struct NCMediaNew: View { weak var dataModelDelegate: DataDelegate? - @EnvironmentObject var hudCoordinator: JGProgressHUDCoordinator - var body: some View { GeometryReader { outerProxy in NavigationView { @@ -219,9 +215,9 @@ struct NCMediaNew: View { } } .onAppear { vm.loadData() } - .fullScreenCover(isPresented: $isMediaViewControllerPresented) { - NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) - } +// .fullScreenCover(isPresented: $isMediaViewControllerPresented) { +// NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) +// } } } From 7087459842bd6117d3db72e4fc36fc8dad50436a Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 18:12:48 +0200 Subject: [PATCH 085/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 5 +-- iOSClient/Media/NCMediaNew.swift | 37 ++++++++++++++++--- iOSClient/Media/NCMediaUI.swift | 6 +++ .../NCViewerMedia/NCViewerMediaPage.swift | 2 + 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index de98970081..6816d455ac 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -29,7 +29,7 @@ struct NCMediaCell: View { })) ZStack(alignment: .center) { - NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { +// NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { if thumbnail.isDefaultImage { image .foregroundColor(Color(uiColor: .systemGray4)) @@ -38,7 +38,7 @@ struct NCMediaCell: View { } else { image } - }.disabled(isInSelectMode) +// }.disabled(isInSelectMode) } .frame(maxWidth: .infinity, maxHeight: .infinity) .overlay(alignment: .bottomLeading) { @@ -76,7 +76,6 @@ struct NCMediaCell: View { } .onChange(of: isInSelectMode) { newValue in isSelected = !newValue - onSelected(thumbnail, isSelected) } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 7c5abd27d0..0dc3a3358d 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -83,21 +83,43 @@ struct NCMediaNew: View { var body: some View { GeometryReader { outerProxy in - NavigationView { +// NavigationView { ZStack(alignment: .top) { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, isSelected in - // TODO: Only do selection here - if isSelected { + if isInSelectMode, isSelected { selectedMetadataInSelectMode.append(tappedThumbnail.metadata) print(selectedMetadataInSelectMode) } else { selectedMetadataInSelectMode.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) print(selectedMetadataInSelectMode) } + + let selectedMetadata = tappedThumbnail.metadata + + if !isInSelectMode { + if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { + var index = 0 + for medatasImage in vm.metadatas { + if medatasImage.ocId == selectedMetadata.ocId { + viewController.currentIndex = index + break + } + index += 1 + } + viewController.metadatas = vm.metadatas + +// NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) + parent.navigationController?.pushViewController(viewController, animated: true) + } + +// dataModelDelegate?.updateData(metadatas: vm.metadatas, selectedMetadata: tappedMetadata, image: tappedThumbnail.image) + // isMediaViewControllerPresented = true +// print(selectedMetadataInSelectMode) + } } } @@ -208,10 +230,13 @@ struct NCMediaNew: View { } } .onAppear { vm.loadData() } - .fullScreenCover(isPresented: $isMediaViewControllerPresented) { - NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) +// .fullScreenCover(isPresented: $isMediaViewControllerPresented) { +// NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) +// } + .onChange(of: isInSelectMode) { newValue in + selectedMetadataInSelectMode.removeAll() } - } +// } } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { diff --git a/iOSClient/Media/NCMediaUI.swift b/iOSClient/Media/NCMediaUI.swift index d4e4a09108..961cee081b 100644 --- a/iOSClient/Media/NCMediaUI.swift +++ b/iOSClient/Media/NCMediaUI.swift @@ -11,8 +11,13 @@ import SwiftUI class NCMediaUI: UIViewController, ObservableObject { + override func viewWillAppear(_ animated: Bool) { + navigationController?.navigationBar.isHidden = true + } + override func viewDidLoad() { super.viewDidLoad() + navigationController?.navigationBar.isHidden = true addView() } @@ -24,6 +29,7 @@ class NCMediaUI: UIViewController, ObservableObject { view.addSubview(controller.view) controller.didMove(toParent: self) + NSLayoutConstraint.activate([ controller.view.widthAnchor.constraint(equalTo: view.widthAnchor), controller.view.heightAnchor.constraint(equalTo: view.heightAnchor), diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 7ec15b5c5d..91d31c163e 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -88,6 +88,8 @@ class NCViewerMediaPage: UIViewController { override func viewDidLoad() { super.viewDidLoad() + navigationController?.navigationBar.isHidden = false + singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didSingleTapWith(gestureRecognizer:))) panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPanWith(gestureRecognizer:))) longtapGestureRecognizer = UILongPressGestureRecognizer() From 8d0d2c1d6202f6baae94c5235b0eda8200b6295b Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 18:30:17 +0200 Subject: [PATCH 086/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Main/Main.storyboard | 4 ++-- iOSClient/Media/NCMediaNew.swift | 11 +++++++++++ iOSClient/Media/NCMediaUI.swift | 1 - 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index 6105de6fe7..557f6a31f9 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -3,7 +3,7 @@ - + @@ -247,7 +247,7 @@ - + diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 0dc3a3358d..90caeac082 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -79,6 +79,8 @@ struct NCMediaNew: View { @State var toolbarItemsColor = Color.blue @State var toolbarColors = [Color.clear] + @State private var showDeleteConfirmation = false + weak var dataModelDelegate: DataDelegate? var body: some View { @@ -175,6 +177,15 @@ struct NCMediaNew: View { if isInSelectMode, !selectedMetadataInSelectMode.isEmpty { ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) + .onTapGesture { + showDeleteConfirmation = true + } + .confirmationDialog("", isPresented: $showDeleteConfirmation) { + Button("Delete selected media", role: .destructive) { + vm.deleteSelectedMetadata() + isInSelectMode = false + } + } } Menu { diff --git a/iOSClient/Media/NCMediaUI.swift b/iOSClient/Media/NCMediaUI.swift index 961cee081b..98abfcd328 100644 --- a/iOSClient/Media/NCMediaUI.swift +++ b/iOSClient/Media/NCMediaUI.swift @@ -10,7 +10,6 @@ import Foundation import SwiftUI class NCMediaUI: UIViewController, ObservableObject { - override func viewWillAppear(_ animated: Bool) { navigationController?.navigationBar.isHidden = true } From 2b7e2fc2325c88146b541831a3ea459b2e7f6358 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 20 Sep 2023 18:45:36 +0200 Subject: [PATCH 087/103] Fix merge issues Signed-off-by: Milen Pivchev --- iOSClient/Main/Main.storyboard | 43 +++++++++++++++++++------------- iOSClient/Media/NCMediaNew.swift | 11 +++----- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index 557f6a31f9..f4a68fdd54 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -7,16 +7,6 @@ - - - - - - - - - - @@ -34,7 +24,7 @@ - + @@ -244,15 +234,34 @@ + + + + + + + + + + - + - - - - + + + + + + + + + + + + + - + diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 90caeac082..7cfe006c12 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -73,7 +73,6 @@ struct NCMediaNew: View { @State private var isMediaViewControllerPresented = false @State private var tappedMetadata = tableMetadata() @State private var isInSelectMode = false - @State private var selectedMetadataInSelectMode: [tableMetadata] = [] @State var titleColor = Color.primary @State var toolbarItemsColor = Color.blue @@ -93,11 +92,9 @@ struct NCMediaNew: View { NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, isSelected in if isInSelectMode, isSelected { - selectedMetadataInSelectMode.append(tappedThumbnail.metadata) - print(selectedMetadataInSelectMode) + vm.selectedMetadatas.append(tappedThumbnail.metadata) } else { - selectedMetadataInSelectMode.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) - print(selectedMetadataInSelectMode) + vm.selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) } let selectedMetadata = tappedThumbnail.metadata @@ -175,7 +172,7 @@ struct NCMediaNew: View { .background(.ultraThinMaterial) .cornerRadius(.infinity) - if isInSelectMode, !selectedMetadataInSelectMode.isEmpty { + if isInSelectMode, !vm.selectedMetadatas.isEmpty { ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) .onTapGesture { showDeleteConfirmation = true @@ -245,7 +242,7 @@ struct NCMediaNew: View { // NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) // } .onChange(of: isInSelectMode) { newValue in - selectedMetadataInSelectMode.removeAll() +// if newValue == false { vm.selectedMetadatas.removeAll() } } // } } From 74dd1a0869af137d54b97403d946b4050ea0f408 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 21 Sep 2023 15:30:09 +0200 Subject: [PATCH 088/103] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 86 ++++++++-------- iOSClient/Media/NCMediaNew.swift | 135 +++++++++++-------------- iOSClient/Media/NCMediaViewModel.swift | 11 +- 3 files changed, 109 insertions(+), 123 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 6816d455ac..f9dc801058 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -29,55 +29,53 @@ struct NCMediaCell: View { })) ZStack(alignment: .center) { -// NavigationLink(destination: NCViewerMediaPageController(metadatas: [thumbnail.metadata], selectedMetadata: thumbnail.metadata)) { - if thumbnail.isDefaultImage { - image - .foregroundColor(Color(uiColor: .systemGray4)) - .scaledToFit() - .frame(width: 40) - } else { - image - } -// }.disabled(isInSelectMode) + if thumbnail.isDefaultImage { + image + .foregroundColor(Color(uiColor: .systemGray4)) + .scaledToFit() + .frame(width: 40) + } else { + image } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .overlay(alignment: .bottomLeading) { - if thumbnail.metadata.isVideo, !thumbnail.isDefaultImage { - Image(systemName: "play.fill") - .resizable() - .foregroundColor(Color(uiColor: .systemGray4)) - .scaledToFit() - .frame(width: 20) - .padding([.leading, .bottom], 10) - } - } - .overlay { - if isInSelectMode, isSelected { - Color.black.opacity(0.6).frame(maxWidth: .infinity) - } - } - .overlay(alignment: .bottomTrailing) { - if isInSelectMode, isSelected { - Image(systemName: "checkmark.circle.fill") - .resizable() - .foregroundColor(.blue) - .background(.white) - .clipShape(Circle()) - .scaledToFit() - .frame(width: 20) - .padding([.trailing, .bottom], 10) - } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .overlay(alignment: .bottomLeading) { + if thumbnail.metadata.isVideo, !thumbnail.isDefaultImage { + Image(systemName: "play.fill") + .resizable() + .foregroundColor(Color(uiColor: .white)) + .scaledToFit() + .frame(width: 16) + .padding([.leading, .bottom], 10) } - .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) - .background(Color(uiColor: .systemGray6)) - .onTapGesture { - if isInSelectMode { isSelected.toggle() } - onSelected(thumbnail, isSelected) + } + .overlay { + if isInSelectMode, isSelected { + Color.black.opacity(0.6).frame(maxWidth: .infinity) } - .onChange(of: isInSelectMode) { newValue in - isSelected = !newValue + } + .overlay(alignment: .bottomTrailing) { + if isInSelectMode, isSelected { + Image(systemName: "checkmark.circle.fill") + .resizable() + .foregroundColor(.blue) + .background(.white) + .clipShape(Circle()) + .scaledToFit() + .frame(width: 20) + .padding([.trailing, .bottom], 10) } } + .frame(width: CGFloat(thumbnail.scaledSize.width * shrinkRatio), height: CGFloat(thumbnail.scaledSize.height * shrinkRatio)) + .background(Color(uiColor: .systemGray6)) + .onTapGesture { + if isInSelectMode { isSelected.toggle() } + onSelected(thumbnail, isSelected) + } + .onChange(of: isInSelectMode) { newValue in + isSelected = !newValue + } + } } struct NCMediaLoadingCell: View { diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 7cfe006c12..f71d66d1b4 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -11,58 +11,58 @@ import PreviewSnapshots import NextcloudKit import VisibilityTrackingScrollView -protocol DataDelegate: AnyObject { - func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) -} - -class NCMediaUIHostingController: UIHostingController, DataDelegate { - required init?(coder aDecoder: NSCoder) { - let view = NCMediaNew() - super.init(coder: aDecoder, rootView: view) - rootView.dataModelDelegate = self - } - - func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) { - if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { - var index = 0 - for medatasImage in metadatas { - if medatasImage.ocId == selectedMetadata.ocId { - viewController.currentIndex = index - break - } - index += 1 - } - viewController.metadatas = metadatas - - NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) - } - } -} - -struct NCViewerMediaPageController: UIViewControllerRepresentable { - let metadatas: [tableMetadata] - let selectedMetadata: tableMetadata - - func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { - - if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { - var index = 0 - for medatasImage in metadatas { - if medatasImage.ocId == selectedMetadata.ocId { - viewController.currentIndex = index - break - } - index += 1 - } - viewController.metadatas = metadatas - return viewController - } else { - return NCViewerMediaPage() - } - } - - func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} -} +//protocol DataDelegate: AnyObject { +// func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) +//} + +//class NCMediaUIHostingController: UIHostingController, DataDelegate { +// required init?(coder aDecoder: NSCoder) { +// let view = NCMediaNew() +// super.init(coder: aDecoder, rootView: view) +// rootView.dataModelDelegate = self +// } +// +// func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) { +// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { +// var index = 0 +// for medatasImage in metadatas { +// if medatasImage.ocId == selectedMetadata.ocId { +// viewController.currentIndex = index +// break +// } +// index += 1 +// } +// viewController.metadatas = metadatas +// +// NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) +// } +// } +//} +// +//struct NCViewerMediaPageController: UIViewControllerRepresentable { +// let metadatas: [tableMetadata] +// let selectedMetadata: tableMetadata +// +// func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { +// +// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { +// var index = 0 +// for medatasImage in metadatas { +// if medatasImage.ocId == selectedMetadata.ocId { +// viewController.currentIndex = index +// break +// } +// index += 1 +// } +// viewController.metadatas = metadatas +// return viewController +// } else { +// return NCViewerMediaPage() +// } +// } +// +// func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} +//} struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @@ -70,9 +70,7 @@ struct NCMediaNew: View { @State private var columns = 2 @State private var title = "Media" @State private var isScrolledToTop = true - @State private var isMediaViewControllerPresented = false @State private var tappedMetadata = tableMetadata() - @State private var isInSelectMode = false @State var titleColor = Color.primary @State var toolbarItemsColor = Color.blue @@ -80,18 +78,15 @@ struct NCMediaNew: View { @State private var showDeleteConfirmation = false - weak var dataModelDelegate: DataDelegate? - var body: some View { GeometryReader { outerProxy in -// NavigationView { ZStack(alignment: .top) { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $isInSelectMode) { tappedThumbnail, isSelected in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) { tappedThumbnail, isSelected in - if isInSelectMode, isSelected { + if vm.isInSelectMode, isSelected { vm.selectedMetadatas.append(tappedThumbnail.metadata) } else { vm.selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) @@ -99,7 +94,7 @@ struct NCMediaNew: View { let selectedMetadata = tappedThumbnail.metadata - if !isInSelectMode { + if !vm.isInSelectMode { if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { var index = 0 for medatasImage in vm.metadatas { @@ -111,13 +106,8 @@ struct NCMediaNew: View { } viewController.metadatas = vm.metadatas -// NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) parent.navigationController?.pushViewController(viewController, animated: true) } - -// dataModelDelegate?.updateData(metadatas: vm.metadatas, selectedMetadata: tappedMetadata, image: tappedThumbnail.image) - // isMediaViewControllerPresented = true -// print(selectedMetadataInSelectMode) } } } @@ -162,9 +152,9 @@ struct NCMediaNew: View { Spacer() Button(action: { - isInSelectMode.toggle() + vm.isInSelectMode.toggle() }, label: { - Text(NSLocalizedString(isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) + Text(NSLocalizedString(vm.isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) .foregroundStyle(toolbarItemsColor) }) .padding(.horizontal, 6) @@ -172,7 +162,7 @@ struct NCMediaNew: View { .background(.ultraThinMaterial) .cornerRadius(.infinity) - if isInSelectMode, !vm.selectedMetadatas.isEmpty { + if vm.isInSelectMode, !vm.selectedMetadatas.isEmpty { ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) .onTapGesture { showDeleteConfirmation = true @@ -180,7 +170,6 @@ struct NCMediaNew: View { .confirmationDialog("", isPresented: $showDeleteConfirmation) { Button("Delete selected media", role: .destructive) { vm.deleteSelectedMetadata() - isInSelectMode = false } } } @@ -238,13 +227,9 @@ struct NCMediaNew: View { } } .onAppear { vm.loadData() } -// .fullScreenCover(isPresented: $isMediaViewControllerPresented) { -// NCViewerMediaPageController(metadatas: vm.metadatas, selectedMetadata: tappedMetadata) -// } - .onChange(of: isInSelectMode) { newValue in -// if newValue == false { vm.selectedMetadatas.removeAll() } + .onChange(of: vm.isInSelectMode) { newValue in + if newValue == false { vm.selectedMetadatas.removeAll() } } -// } } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 4aa3ad9649..00549f562b 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -14,9 +14,10 @@ enum SortType: String { } @MainActor class NCMediaViewModel: ObservableObject { - @Published var metadatas: [tableMetadata] = [] - @Published var selectedMetadatas: [tableMetadata] = [] - + @Published private(set) var metadatas: [tableMetadata] = [] + @Published internal var selectedMetadatas: [tableMetadata] = [] + @Published internal var isInSelectMode = false + private var account: String = "" private var lastContentOffsetY: CGFloat = 0 private var mediaPath = "" @@ -24,6 +25,7 @@ enum SortType: String { private var predicateDefault: NSPredicate? private var predicate: NSPredicate? private let appDelegate = UIApplication.shared.delegate as? AppDelegate + @Published internal var filterClassTypeImage = false @Published internal var filterClassTypeVideo = false @@ -45,7 +47,6 @@ enum SortType: String { $filterClassTypeImage.sink { _ in self.loadData() }.store(in: &cancellables) $filterClassTypeVideo.sink { _ in self.loadData() }.store(in: &cancellables) $sortType.sink { sortType in - print(sortType.rawValue) CCUtility.setMediaSortDate(sortType.rawValue) self.loadData() } @@ -137,6 +138,8 @@ enum SortType: String { } } NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "onlyLocalCache": false, "error": error]) + + isInSelectMode = false } // completion?() From 8ce1e3d81c433d3df102d8fa3752ecfdcad98347 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 21 Sep 2023 15:58:40 +0200 Subject: [PATCH 089/103] Refactor Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 20 +- iOSClient/Main/Main.storyboard | 22 +-- iOSClient/Media/NCMediaNew.swift | 21 +- iOSClient/Media/NCMediaUI.storyboard | 27 --- iOSClient/Media/NCMediaViewModel.swift | 186 +++++++----------- .../NCMediaUIKitWrapper.swift} | 23 +-- 6 files changed, 100 insertions(+), 199 deletions(-) delete mode 100644 iOSClient/Media/NCMediaUI.storyboard rename iOSClient/Media/{NCMediaUI.swift => UIWrapper/NCMediaUIKitWrapper.swift} (65%) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index bc997b712f..28e6dc8855 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -347,8 +347,7 @@ F7501C322212E57500FB1415 /* NCMedia.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7501C302212E57400FB1415 /* NCMedia.storyboard */; }; F7501C332212E57500FB1415 /* NCMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7501C312212E57400FB1415 /* NCMedia.swift */; }; F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */ = {isa = PBXBuildFile; productRef = F753BA92281FD8020015BFB6 /* EasyTipView */; }; - F7556ED62ABB3A5F00350BD4 /* NCMediaUI.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */; }; - F7556ED82ABB3C2100350BD4 /* NCMediaUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7556ED72ABB3C2100350BD4 /* NCMediaUI.swift */; }; + F7556ED82ABB3C2100350BD4 /* NCMediaUIKitWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7556ED72ABB3C2100350BD4 /* NCMediaUIKitWrapper.swift */; }; F755BD9B20594AC7008C5FBB /* NCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F755BD9A20594AC7008C5FBB /* NCService.swift */; }; F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; }; F757CC8329E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; }; @@ -1053,8 +1052,7 @@ F753701822723D620041C76C /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = ""; }; F753701922723E0D0041C76C /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; F753701A22723EC80041C76C /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; - F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCMediaUI.storyboard; sourceTree = ""; }; - F7556ED72ABB3C2100350BD4 /* NCMediaUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaUI.swift; sourceTree = ""; }; + F7556ED72ABB3C2100350BD4 /* NCMediaUIKitWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaUIKitWrapper.swift; sourceTree = ""; }; F755BD9A20594AC7008C5FBB /* NCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCService.swift; sourceTree = ""; }; F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Groupfolders.swift"; sourceTree = ""; }; F757CC8A29E82D0500F31428 /* NCGroupfolders.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCGroupfolders.storyboard; sourceTree = ""; }; @@ -1628,6 +1626,14 @@ path = NextcloudIntegrationTests; sourceTree = ""; }; + F30A6C042ABC7E6500148857 /* UIWrapper */ = { + isa = PBXGroup; + children = ( + F7556ED72ABB3C2100350BD4 /* NCMediaUIKitWrapper.swift */, + ); + path = UIWrapper; + sourceTree = ""; + }; F30A962A2A27A9C800D7BCFE /* Tests */ = { isa = PBXGroup; children = ( @@ -2412,10 +2418,9 @@ F7EC9CB921185F2000F1C5CE /* Media */ = { isa = PBXGroup; children = ( + F30A6C042ABC7E6500148857 /* UIWrapper */, F720B5B72507B9A5008C94E5 /* Cell */, F7501C302212E57400FB1415 /* NCMedia.storyboard */, - F7556ED52ABB3A5F00350BD4 /* NCMediaUI.storyboard */, - F7556ED72ABB3C2100350BD4 /* NCMediaUI.swift */, F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */, F7501C312212E57400FB1415 /* NCMedia.swift */, F3F7ACFD2A98C56C00AE12CF /* NCMediaNew.swift */, @@ -3228,7 +3233,6 @@ files = ( F7362A1F220C853A005101B5 /* LaunchScreen.storyboard in Resources */, F77444F622281649000D5EB0 /* NCGridMediaCell.xib in Resources */, - F7556ED62ABB3A5F00350BD4 /* NCMediaUI.storyboard in Resources */, F78ACD4421903CF20088454D /* NCListCell.xib in Resources */, F3BB464D2A39ADCC00461F6E /* NCMoreAppSuggestionsCell.xib in Resources */, F7F4F10727ECDBDB008676F9 /* Inconsolata-Black.ttf in Resources */, @@ -3880,7 +3884,7 @@ F79FFB262A97C24A0055EEA4 /* NCNetworkingE2EEMarkFolder.swift in Sources */, F70D8D8124A4A9BF000A5756 /* NCNetworkingProcessUpload.swift in Sources */, F7D96FCC246ED7E200536D73 /* NCNetworkingCheckRemoteUser.swift in Sources */, - F7556ED82ABB3C2100350BD4 /* NCMediaUI.swift in Sources */, + F7556ED82ABB3C2100350BD4 /* NCMediaUIKitWrapper.swift in Sources */, F7E4D9C422ED929B003675FD /* NCShareCommentsCell.swift in Sources */, F717402E24F699A5000C87D5 /* NCFavorite.swift in Sources */, AF2D7C7E2742559100ADF566 /* NCShareUserCell.swift in Sources */, diff --git a/iOSClient/Main/Main.storyboard b/iOSClient/Main/Main.storyboard index f4a68fdd54..70169a3427 100644 --- a/iOSClient/Main/Main.storyboard +++ b/iOSClient/Main/Main.storyboard @@ -234,16 +234,6 @@ - - - - - - - - - - @@ -256,13 +246,23 @@ - + + + + + + + + + + + diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index f71d66d1b4..719f7b9f72 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -66,7 +66,7 @@ import VisibilityTrackingScrollView struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() - @EnvironmentObject var parent: NCMediaUI + @EnvironmentObject var parent: NCMediaUIKitWrapper @State private var columns = 2 @State private var title = "Media" @State private var isScrolledToTop = true @@ -92,22 +92,11 @@ struct NCMediaNew: View { vm.selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) } - let selectedMetadata = tappedThumbnail.metadata if !vm.isInSelectMode { - if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { - var index = 0 - for medatasImage in vm.metadatas { - if medatasImage.ocId == selectedMetadata.ocId { - viewController.currentIndex = index - break - } - index += 1 - } - viewController.metadatas = vm.metadatas - - parent.navigationController?.pushViewController(viewController, animated: true) - } + let selectedMetadata = tappedThumbnail.metadata + vm.onCellTapped(metadata: selectedMetadata) + NCViewer.shared.view(viewController: parent, metadata: selectedMetadata, metadatas: vm.metadatas, imageIcon: tappedThumbnail.image) } } } @@ -226,7 +215,7 @@ struct NCMediaNew: View { columns = 2 } } - .onAppear { vm.loadData() } + .onAppear { vm.loadMediaFromDB() } .onChange(of: vm.isInSelectMode) { newValue in if newValue == false { vm.selectedMetadatas.removeAll() } } diff --git a/iOSClient/Media/NCMediaUI.storyboard b/iOSClient/Media/NCMediaUI.storyboard deleted file mode 100644 index 99c08b5744..0000000000 --- a/iOSClient/Media/NCMediaUI.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 00549f562b..0cfb20d388 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -10,7 +10,7 @@ import NextcloudKit import Combine enum SortType: String { - case modifiedDate = "date", creationDate = "creationDate", uploadDate = "uploadDate" + case modifiedDate = "date", creationDate = "creationDate", uploadDate = "uploadDate" } @MainActor class NCMediaViewModel: ObservableObject { @@ -42,13 +42,13 @@ enum SortType: String { NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) - searchNewMedia() + loadNewMedia() - $filterClassTypeImage.sink { _ in self.loadData() }.store(in: &cancellables) - $filterClassTypeVideo.sink { _ in self.loadData() }.store(in: &cancellables) + $filterClassTypeImage.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) + $filterClassTypeVideo.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) $sortType.sink { sortType in CCUtility.setMediaSortDate(sortType.rawValue) - self.loadData() + self.loadMediaFromDB() } .store(in: &cancellables) } @@ -61,17 +61,6 @@ enum SortType: String { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) } - func loadData() { - guard let appDelegate, !appDelegate.account.isEmpty else { return } - - if account != appDelegate.account { - self.metadatas = [] - account = appDelegate.account - } - - self.queryDB(isForced: true) - } - private func queryDB(isForced: Bool = false) { guard let appDelegate else { return } @@ -112,12 +101,12 @@ enum SortType: String { } func loadMoreItems() { - searchOldMedia() + loadOldMedia() needsLoadingMoreItems = false } func onPullToRefresh() { - searchNewMedia() + loadNewMedia() } func onCellTapped(metadata: tableMetadata) { @@ -128,21 +117,19 @@ enum SortType: String { let notLocked = selectedMetadatas.allSatisfy { !$0.lock } if notLocked { - Task { - var error = NKError() - var ocId: [String] = [] - for metadata in selectedMetadatas where error == .success { - error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false) - if error == .success { - ocId.append(metadata.ocId) - } + Task { + var error = NKError() + var ocId: [String] = [] + for metadata in selectedMetadatas where error == .success { + error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false) + if error == .success { + ocId.append(metadata.ocId) } - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "onlyLocalCache": false, "error": error]) - - isInSelectMode = false } -// completion?() + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "onlyLocalCache": false, "error": error]) + isInSelectMode = false + } } } } @@ -154,21 +141,22 @@ extension NCMediaViewModel { guard let userInfo = notification.userInfo as NSDictionary?, let error = userInfo["error"] as? NKError else { return } - loadData() + loadMediaFromDB() if error != .success { NCContentPresenter.shared.showError(error: error) } - - // if let hud = userInfo["hud"] as? JGProgressHUD { - // hud.dismiss() - // } } @objc func moveFile(_ notification: NSNotification) { - guard let userInfo = notification.userInfo as NSDictionary? else { return } + guard let userInfo = notification.userInfo as NSDictionary?, + let error = userInfo["error"] as? NKError else { return } - loadData() + loadMediaFromDB() + + if error != .success { + NCContentPresenter.shared.showError(error: error) + } } @objc func copyFile(_ notification: NSNotification) { @@ -181,7 +169,7 @@ extension NCMediaViewModel { account == appDelegate?.account else { return } - self.loadData() + self.loadMediaFromDB() } @objc func uploadedFile(_ notification: NSNotification) { @@ -192,25 +180,25 @@ extension NCMediaViewModel { account == appDelegate?.account else { return } - self.loadData() + self.loadMediaFromDB() } } -// MARK: - Search media +// MARK: - Load media extension NCMediaViewModel { - private func searchOldMedia(value: Int = -30, limit: Int = 300) { - -// if oldInProgress { return } else { oldInProgress = true } -// DispatchQueue.main.async { -// self.collectionView.reloadData() -// var bottom: CGFloat = 0 -// if let mainTabBar = self.tabBarController?.tabBar as? NCMainTabBar { -// bottom = -mainTabBar.getHight() -// } -// NCActivityIndicator.shared.start(backgroundView: self.view, bottom: bottom - 5, style: .medium) -// } + func loadMediaFromDB() { + guard let appDelegate, !appDelegate.account.isEmpty else { return } + if account != appDelegate.account { + self.metadatas = [] + account = appDelegate.account + } + + self.queryDB(isForced: true) + } + + private func loadOldMedia(value: Int = -30, limit: Int = 300) { var lessDate = Date() if predicateDefault != nil { if let metadata = NCManageDatabase.shared.getMetadata(predicate: predicateDefault!, sorted: "date", ascending: true) { @@ -228,13 +216,6 @@ extension NCMediaViewModel { let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) NextcloudKit.shared.searchMedia(path: mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in -// -//// self.oldInProgress = false -// DispatchQueue.main.async { -// NCActivityIndicator.shared.stop() -// self.loadData() -//// self.collectionView.reloadData() -// } if error == .success && account == self.appDelegate?.account { if !files.isEmpty { @@ -244,13 +225,13 @@ extension NCMediaViewModel { let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) let metadatasChanged = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) if metadatasChanged.metadatasUpdate.isEmpty { - self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: true) + self.reloadOldMedia(value: value, limit: limit, withElseReloadDataSource: true) } else { - self.loadData() + self.loadMediaFromDB() } } } else { - self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: false) + self.reloadOldMedia(value: value, limit: limit, withElseReloadDataSource: false) } } else if error != .success { NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Media search old media error code \(error.errorCode) " + error.errorDescription) @@ -258,81 +239,46 @@ extension NCMediaViewModel { } } - private func researchOldMedia(value: Int, limit: Int, withElseReloadDataSource: Bool) { - + private func reloadOldMedia(value: Int, limit: Int, withElseReloadDataSource: Bool) { if value == -30 { - searchOldMedia(value: -90) + loadOldMedia(value: -90) } else if value == -90 { - searchOldMedia(value: -180) + loadOldMedia(value: -180) } else if value == -180 { - searchOldMedia(value: -999) + loadOldMedia(value: -999) } else if value == -999 && limit > 0 { - searchOldMedia(value: -999, limit: 0) + loadOldMedia(value: -999, limit: 0) } else { if withElseReloadDataSource { - loadData() + loadMediaFromDB() } } } -// @objc func searchNewMediaTimer() { -// self.searchNewMedia() -// } -// - @objc func searchNewMedia() { - -// if newInProgress { return } else { -// newInProgress = true -// mediaCommandView?.activityIndicator.startAnimating() -// } - + @objc func loadNewMedia() { var limit: Int = 1000 guard var lessDate = Calendar.current.date(byAdding: .second, value: 1, to: Date()) else { return } guard var greaterDate = Calendar.current.date(byAdding: .day, value: -30, to: Date()) else { return } -// if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) { -// if let cell = visibleCells.first as? NCGridMediaCell { -// if cell.date != nil { -// if cell.date != self.metadatas.first?.date as Date? { -// lessDate = Calendar.current.date(byAdding: .second, value: 1, to: cell.date!)! -// limit = 0 -// } -// } -// } -// if let cell = visibleCells.last as? NCGridMediaCell { -// if cell.date != nil { -// greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: cell.date!)! -// } -// } -// } - -// reloadDataThenPerform { - - let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) - - NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, data, error in - -// self.newInProgress = false -// DispatchQueue.main.async { -// self.mediaCommandView?.activityIndicator.stopAnimating() -// } - - if error == .success && account == self.appDelegate?.account && files.count > 0 { - NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in - let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) - let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.predicate!]) - let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) - let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) - if updateMetadatas.metadatasUpdate.count > 0 || updateMetadatas.metadatasDelete.count > 0 { - self.loadData() - } + let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + + NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, data, error in + + if error == .success && account == self.appDelegate?.account && files.count > 0 { + NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in + let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) + let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.predicate!]) + let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) + let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) + if updateMetadatas.metadatasUpdate.count > 0 || updateMetadatas.metadatasDelete.count > 0 { + self.loadMediaFromDB() } - } else if error == .success && files.count == 0 && self.metadatas.count == 0 { - self.searchOldMedia() - } else if error != .success { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) } + } else if error == .success && files.count == 0 && self.metadatas.count == 0 { + self.loadOldMedia() + } else if error != .success { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) } -// } + } } } diff --git a/iOSClient/Media/NCMediaUI.swift b/iOSClient/Media/UIWrapper/NCMediaUIKitWrapper.swift similarity index 65% rename from iOSClient/Media/NCMediaUI.swift rename to iOSClient/Media/UIWrapper/NCMediaUIKitWrapper.swift index 98abfcd328..41344f0f21 100644 --- a/iOSClient/Media/NCMediaUI.swift +++ b/iOSClient/Media/UIWrapper/NCMediaUIKitWrapper.swift @@ -9,7 +9,10 @@ import Foundation import SwiftUI -class NCMediaUI: UIViewController, ObservableObject { +/** + Wraps the SwiftUI view to a ViewController with a NavigationViewController + */ +class NCMediaUIKitWrapper: UIViewController, ObservableObject { override func viewWillAppear(_ animated: Bool) { navigationController?.navigationBar.isHidden = true } @@ -21,8 +24,8 @@ class NCMediaUI: UIViewController, ObservableObject { } func addView() { - let testView = NCMediaNew() - let controller = UIHostingController(rootView: testView.environmentObject(self)) + let mediaView = NCMediaNew() + let controller = UIHostingController(rootView: mediaView.environmentObject(self)) addChild(controller) controller.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(controller.view) @@ -37,17 +40,3 @@ class NCMediaUI: UIViewController, ObservableObject { ]) } } - -/* -struct SwiftUIView: View { - @EnvironmentObject var parent: NCMediaUI - var body: some View { - Button { - // push new view controller - // parent.navigationController?.pushViewController(<#T##viewController: UIViewController##UIViewController#>, animated: <#T##Bool#>) - } label: { - Text("go") - } - } -} -*/ From 059f430157ad3fba9072df5c6bf6015ad10f4231 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 22 Sep 2023 09:52:06 +0200 Subject: [PATCH 090/103] Add pull to refresh Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 33 ++++++++++++ iOSClient/Media/Cell/NCMediaCell.swift | 2 +- iOSClient/Media/NCMediaNew.swift | 75 ++++++-------------------- 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 28e6dc8855..505f9fe73f 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -91,6 +91,9 @@ F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; + F30A6C072ABCA34200148857 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C062ABCA34200148857 /* SwiftUIIntrospect */; }; + F30A6C092ABCA34200148857 /* SwiftUIIntrospect-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C082ABCA34200148857 /* SwiftUIIntrospect-Dynamic */; }; + F30A6C0B2ABCA34200148857 /* SwiftUIIntrospect-Static in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C0A2ABCA34200148857 /* SwiftUIIntrospect-Static */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -1509,14 +1512,17 @@ buildActionMask = 2147483647; files = ( F7D56B1A2972405500FA46C4 /* Mantis in Frameworks */, + F30A6C092ABCA34200148857 /* SwiftUIIntrospect-Dynamic in Frameworks */, F7ED547C25EEA65400956C55 /* QRCodeReader in Frameworks */, F788ECC7263AAAFA00ADC67F /* MarkdownKit in Frameworks */, F77BC3EB293E5268005F2B08 /* Swifter in Frameworks */, + F30A6C072ABCA34200148857 /* SwiftUIIntrospect in Frameworks */, F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */, F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */, F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */, F3C0FB702AB06AB9007E7E30 /* VisibilityTrackingScrollView in Frameworks */, F770768E263A8C3400A1BA94 /* FloatingPanel in Frameworks */, + F30A6C0B2ABCA34200148857 /* SwiftUIIntrospect-Static in Frameworks */, F710FC7C277B7D0000AA9FBF /* RealmSwift in Frameworks */, F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */, F77333882927A72100466E35 /* OpenSSL in Frameworks */, @@ -2942,6 +2948,9 @@ F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, F3EFBF5E2AB2100E00B5724C /* Shimmer */, F30A6BEF2AB4AAB700148857 /* Refreshable */, + F30A6C062ABCA34200148857 /* SwiftUIIntrospect */, + F30A6C082ABCA34200148857 /* SwiftUIIntrospect-Dynamic */, + F30A6C0A2ABCA34200148857 /* SwiftUIIntrospect-Static */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -3120,6 +3129,7 @@ F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */, + F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4969,6 +4979,14 @@ version = 0.2.0; }; }; + F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; F31F694B2A2F6EFA00162F76 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; @@ -5221,6 +5239,21 @@ package = F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */; productName = Refreshable; }; + F30A6C062ABCA34200148857 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + package = F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; + }; + F30A6C082ABCA34200148857 /* SwiftUIIntrospect-Dynamic */ = { + isa = XCSwiftPackageProductDependency; + package = F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = "SwiftUIIntrospect-Dynamic"; + }; + F30A6C0A2ABCA34200148857 /* SwiftUIIntrospect-Static */ = { + isa = XCSwiftPackageProductDependency; + package = F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = "SwiftUIIntrospect-Static"; + }; F30A96332A2DFCD000D7BCFE /* Realm */ = { isa = XCSwiftPackageProductDependency; package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index f9dc801058..ccbf5b48da 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -15,8 +15,8 @@ struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat @Binding var isInSelectMode: Bool - let onSelected: (ScaledThumbnail, Bool) -> Void @State private var isSelected = false + let onSelected: (ScaledThumbnail, Bool) -> Void var body: some View { let image = Image(uiImage: thumbnail.image) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 719f7b9f72..28052c0f99 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -10,59 +10,7 @@ import SwiftUI import PreviewSnapshots import NextcloudKit import VisibilityTrackingScrollView - -//protocol DataDelegate: AnyObject { -// func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) -//} - -//class NCMediaUIHostingController: UIHostingController, DataDelegate { -// required init?(coder aDecoder: NSCoder) { -// let view = NCMediaNew() -// super.init(coder: aDecoder, rootView: view) -// rootView.dataModelDelegate = self -// } -// -// func updateData(metadatas: [tableMetadata], selectedMetadata: tableMetadata, image: UIImage) { -// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { -// var index = 0 -// for medatasImage in metadatas { -// if medatasImage.ocId == selectedMetadata.ocId { -// viewController.currentIndex = index -// break -// } -// index += 1 -// } -// viewController.metadatas = metadatas -// -// NCViewer.shared.view(viewController: self, metadata: selectedMetadata, metadatas: metadatas, imageIcon: image) -// } -// } -//} -// -//struct NCViewerMediaPageController: UIViewControllerRepresentable { -// let metadatas: [tableMetadata] -// let selectedMetadata: tableMetadata -// -// func makeUIViewController(context: UIViewControllerRepresentableContext) -> NCViewerMediaPage { -// -// if let viewController = UIStoryboard(name: "NCViewerMediaPage", bundle: nil).instantiateInitialViewController() as? NCViewerMediaPage { -// var index = 0 -// for medatasImage in metadatas { -// if medatasImage.ocId == selectedMetadata.ocId { -// viewController.currentIndex = index -// break -// } -// index += 1 -// } -// viewController.metadatas = metadatas -// return viewController -// } else { -// return NCViewerMediaPage() -// } -// } -// -// func updateUIViewController(_ uiViewController: NCViewerMediaPage, context: UIViewControllerRepresentableContext) {} -//} +@_spi(Advanced) import SwiftUIIntrospect struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @@ -92,7 +40,6 @@ struct NCMediaNew: View { vm.selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) } - if !vm.isInSelectMode { let selectedMetadata = tappedThumbnail.metadata vm.onCellTapped(metadata: selectedMetadata) @@ -126,8 +73,16 @@ struct NCMediaNew: View { } .refreshable { + try? await Task.sleep(nanoseconds: 1_000_000_000) vm.onPullToRefresh() } + // Not possible to move the refresh control view via SwiftUI, so we have to introspect the internal UIKit views to move it. + // TODO: Maybe .contentMargins() will resolve this but it's iOS 17+ + .introspect(.scrollView, on: .iOS(.v15...)) { scrollView in + scrollView.refreshControl?.translatesAutoresizingMaskIntoConstraints = false + scrollView.refreshControl?.topAnchor.constraint(equalTo: parent.view.topAnchor, constant: 120).isActive = true + scrollView.refreshControl?.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true + } .coordinateSpace(name: "scroll") // Toolbar @@ -191,12 +146,12 @@ struct NCMediaNew: View { }) } - Picker("Sorting options", selection: $vm.sortType) { - Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) - Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) - Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) - } - .pickerStyle(.menu) +// Picker("Sorting options", selection: $vm.sortType) { +// Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) +// Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) +// Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) +// } +// .pickerStyle(.menu) } label: { ToolbarCircularButton(imageSystemName: "ellipsis", toolbarItemsColor: $toolbarItemsColor) } From 2f4da2c99fa87e2d19339d71cf5d7803e2c256df Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 22 Sep 2023 10:53:46 +0200 Subject: [PATCH 091/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 3 +- iOSClient/Media/NCMediaViewModel.swift | 71 +++++++++++++++----------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 28052c0f99..a347d02ba7 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -73,8 +73,7 @@ struct NCMediaNew: View { } .refreshable { - try? await Task.sleep(nanoseconds: 1_000_000_000) - vm.onPullToRefresh() + await vm.onPullToRefresh() } // Not possible to move the refresh control view via SwiftUI, so we have to introspect the internal UIKit views to move it. // TODO: Maybe .contentMargins() will resolve this but it's iOS 17+ diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 0cfb20d388..5e6b2dfd0a 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -9,9 +9,9 @@ import NextcloudKit import Combine -enum SortType: String { - case modifiedDate = "date", creationDate = "creationDate", uploadDate = "uploadDate" -} +//enum SortType: String { +// case modifiedDate = "date", creationDate = "creationDate", uploadDate = "uploadDate" +//} @MainActor class NCMediaViewModel: ObservableObject { @Published private(set) var metadatas: [tableMetadata] = [] @@ -29,7 +29,7 @@ enum SortType: String { @Published internal var filterClassTypeImage = false @Published internal var filterClassTypeVideo = false - @Published internal var sortType: SortType = SortType(rawValue: CCUtility.getMediaSortDate()) ?? .modifiedDate +// @Published internal var sortType: SortType = SortType(rawValue: CCUtility.getMediaSortDate()) ?? .modifiedDate private var cancellables: Set = [] @@ -41,16 +41,19 @@ enum SortType: String { NotificationCenter.default.addObserver(self, selector: #selector(copyFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(uploadedFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(userChanged(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil) - loadNewMedia() + Task { + await loadNewMedia() + } $filterClassTypeImage.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) $filterClassTypeVideo.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) - $sortType.sink { sortType in - CCUtility.setMediaSortDate(sortType.rawValue) - self.loadMediaFromDB() - } - .store(in: &cancellables) +// $sortType.sink { sortType in +// CCUtility.setMediaSortDate(sortType.rawValue) +// self.loadMediaFromDB() +// } +// .store(in: &cancellables) } deinit { @@ -105,8 +108,8 @@ enum SortType: String { needsLoadingMoreItems = false } - func onPullToRefresh() { - loadNewMedia() + func onPullToRefresh() async { + await loadNewMedia() } func onCellTapped(metadata: tableMetadata) { @@ -182,6 +185,12 @@ extension NCMediaViewModel { self.loadMediaFromDB() } + + @objc func userChanged(_ notification: NSNotification) { + Task { + await loadNewMedia() + } + } } // MARK: - Load media @@ -255,29 +264,33 @@ extension NCMediaViewModel { } } - @objc func loadNewMedia() { - var limit: Int = 1000 - guard var lessDate = Calendar.current.date(byAdding: .second, value: 1, to: Date()) else { return } - guard var greaterDate = Calendar.current.date(byAdding: .day, value: -30, to: Date()) else { return } + private func loadNewMedia() async { + let limit: Int = 1000 + guard let lessDate = Calendar.current.date(byAdding: .second, value: 1, to: Date()) else { return } + guard let greaterDate = Calendar.current.date(byAdding: .day, value: -30, to: Date()) else { return } let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) - NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, data, error in + return await withCheckedContinuation { continuation in + NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in - if error == .success && account == self.appDelegate?.account && files.count > 0 { - NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in - let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) - let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.predicate!]) - let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) - let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) - if updateMetadatas.metadatasUpdate.count > 0 || updateMetadatas.metadatasDelete.count > 0 { - self.loadMediaFromDB() + if error == .success && account == self.appDelegate?.account && !files.isEmpty { + NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in + let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate) + let predicateResult = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.predicate!]) + let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult) + let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false) + if !updateMetadatas.metadatasUpdate.isEmpty || !updateMetadatas.metadatasDelete.isEmpty { + self.loadMediaFromDB() + } } + } else if error == .success && files.isEmpty && self.metadatas.isEmpty { + self.loadOldMedia() + } else if error != .success { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) } - } else if error == .success && files.count == 0 && self.metadatas.count == 0 { - self.loadOldMedia() - } else if error != .success { - NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) + + continuation.resume() } } } From ac04b01331159a8d875a0c420f516a3b85183c21 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 22 Sep 2023 13:56:55 +0200 Subject: [PATCH 092/103] Solve thread issues Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 90 +++++++++++++------ .../Extensions/SwiftUIView+Extensions.swift | 24 +++++ iOSClient/Media/NCMediaNew.swift | 18 ++-- iOSClient/Media/NCMediaViewModel.swift | 29 +++--- 4 files changed, 111 insertions(+), 50 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 505f9fe73f..288c0c38ec 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -76,6 +76,13 @@ C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; + F3077B7C2ABDA06A0013BA13 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F3077B7B2ABDA06A0013BA13 /* SwiftUIIntrospect */; }; + F3077B7E2ABDA0FD0013BA13 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F3077B7D2ABDA0FD0013BA13 /* SwiftUIIntrospect */; }; + F3077B802ABDA10B0013BA13 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F3077B7F2ABDA10B0013BA13 /* SwiftUIIntrospect */; }; + F3077B822ABDA1120013BA13 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F3077B812ABDA1120013BA13 /* SwiftUIIntrospect */; }; + F3077B842ABDA1190013BA13 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F3077B832ABDA1190013BA13 /* SwiftUIIntrospect */; }; + F3077B862ABDA1200013BA13 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F3077B852ABDA1200013BA13 /* SwiftUIIntrospect */; }; + F3077B882ABDA1240013BA13 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F3077B872ABDA1240013BA13 /* SwiftUIIntrospect */; }; F30A6BF02AB4AAB700148857 /* Refreshable in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6BEF2AB4AAB700148857 /* Refreshable */; }; F30A6BF22AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; F30A6BF32AB4B31200148857 /* PreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF12AB4B31200148857 /* PreferenceKeys.swift */; }; @@ -91,9 +98,6 @@ F30A6BFE2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6BFF2ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; F30A6C002ABAE84A00148857 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A6BF92ABAE84900148857 /* Binding+Extension.swift */; }; - F30A6C072ABCA34200148857 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C062ABCA34200148857 /* SwiftUIIntrospect */; }; - F30A6C092ABCA34200148857 /* SwiftUIIntrospect-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C082ABCA34200148857 /* SwiftUIIntrospect-Dynamic */; }; - F30A6C0B2ABCA34200148857 /* SwiftUIIntrospect-Static in Frameworks */ = {isa = PBXBuildFile; productRef = F30A6C0A2ABCA34200148857 /* SwiftUIIntrospect-Static */; }; F30A962F2A27ADF900D7BCFE /* EnvVars.stencil in Resources */ = {isa = PBXBuildFile; fileRef = F30A962E2A27ADF900D7BCFE /* EnvVars.stencil */; }; F30A96312A27AEBF00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; F30A96322A27AEDD00D7BCFE /* EnvVars.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30A96302A27AEBF00D7BCFE /* EnvVars.generated.swift */; }; @@ -1404,6 +1408,7 @@ F72AD71128C24BBB006CB92D /* NextcloudKit in Frameworks */, F314F1112A30C11200BC7FAB /* PreviewSnapshots in Frameworks */, F710FC88277B7D3F00AA9FBF /* RealmSwift in Frameworks */, + F3077B882ABDA1240013BA13 /* SwiftUIIntrospect in Frameworks */, F7EBCDD3277B821700A4EF67 /* UICKeyChainStore in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1462,6 +1467,7 @@ F7490E8B29882CE4009DCE94 /* NextcloudKit in Frameworks */, F7490E8929882CC8009DCE94 /* SwiftEntryKit in Frameworks */, F7490E7229882BB4009DCE94 /* RealmSwift in Frameworks */, + F3077B862ABDA1200013BA13 /* SwiftUIIntrospect in Frameworks */, F70716F929881CFA00E72C1D /* UICKeyChainStore in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1473,6 +1479,7 @@ F710FC80277B7D2700AA9FBF /* RealmSwift in Frameworks */, F72AD70F28C24BA1006CB92D /* NextcloudKit in Frameworks */, F72CD01227A7E92400E59476 /* JGProgressHUD in Frameworks */, + F3077B822ABDA1120013BA13 /* SwiftUIIntrospect in Frameworks */, F77CB6A92AA08053000C3CA4 /* OpenSSL in Frameworks */, F73ADD2126554F8E0069EA0D /* SwiftEntryKit in Frameworks */, F7EBCDCF277B81FF00A4EF67 /* UICKeyChainStore in Frameworks */, @@ -1493,6 +1500,7 @@ F7346E2928B0FFF2006CE2D2 /* RealmSwift in Frameworks */, F783030D28B4C59A00B84583 /* SwiftEntryKit in Frameworks */, F7346E1228B0EF5B006CE2D2 /* WidgetKit.framework in Frameworks */, + F3077B7E2ABDA0FD0013BA13 /* SwiftUIIntrospect in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1503,6 +1511,7 @@ F7EBCDD1277B820D00A4EF67 /* UICKeyChainStore in Frameworks */, F73ADD2426554FE20069EA0D /* SwiftEntryKit in Frameworks */, F710FC84277B7D3500AA9FBF /* RealmSwift in Frameworks */, + F3077B842ABDA1190013BA13 /* SwiftUIIntrospect in Frameworks */, F72AD71328C24BCC006CB92D /* NextcloudKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1512,17 +1521,15 @@ buildActionMask = 2147483647; files = ( F7D56B1A2972405500FA46C4 /* Mantis in Frameworks */, - F30A6C092ABCA34200148857 /* SwiftUIIntrospect-Dynamic in Frameworks */, F7ED547C25EEA65400956C55 /* QRCodeReader in Frameworks */, F788ECC7263AAAFA00ADC67F /* MarkdownKit in Frameworks */, F77BC3EB293E5268005F2B08 /* Swifter in Frameworks */, - F30A6C072ABCA34200148857 /* SwiftUIIntrospect in Frameworks */, + F3077B7C2ABDA06A0013BA13 /* SwiftUIIntrospect in Frameworks */, F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */, F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */, F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */, F3C0FB702AB06AB9007E7E30 /* VisibilityTrackingScrollView in Frameworks */, F770768E263A8C3400A1BA94 /* FloatingPanel in Frameworks */, - F30A6C0B2ABCA34200148857 /* SwiftUIIntrospect-Static in Frameworks */, F710FC7C277B7D0000AA9FBF /* RealmSwift in Frameworks */, F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */, F77333882927A72100466E35 /* OpenSSL in Frameworks */, @@ -1554,6 +1561,7 @@ F7A8D72828F17728008BBE1C /* RealmSwift in Frameworks */, F7A8D72E28F17764008BBE1C /* UICKeyChainStore in Frameworks */, F7A8D72428F1771B008BBE1C /* NextcloudKit in Frameworks */, + F3077B802ABDA10B0013BA13 /* SwiftUIIntrospect in Frameworks */, F7C9739228F17131002C43E2 /* Intents.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2696,6 +2704,7 @@ F7EBCDD2277B821700A4EF67 /* UICKeyChainStore */, F72AD71028C24BBB006CB92D /* NextcloudKit */, F314F1102A30C11200BC7FAB /* PreviewSnapshots */, + F3077B872ABDA1240013BA13 /* SwiftUIIntrospect */, ); productName = "Notification Service Extension"; productReference = 2C33C47F23E2C475005F963B /* Notification Service Extension.appex */; @@ -2816,6 +2825,7 @@ F7490E7129882BB4009DCE94 /* RealmSwift */, F7490E8829882CC8009DCE94 /* SwiftEntryKit */, F7490E8A29882CE4009DCE94 /* NextcloudKit */, + F3077B852ABDA1200013BA13 /* SwiftUIIntrospect */, ); productName = "File Provider Extension UI"; productReference = F70716E32987F81500E72C1D /* File Provider Extension UI.appex */; @@ -2844,6 +2854,7 @@ F70821D729E59E6D001CA2D7 /* TagListView */, F7F623B62A5EFA0C0022D3D4 /* Gzip */, F77CB6A82AA08053000C3CA4 /* OpenSSL */, + F3077B812ABDA1120013BA13 /* SwiftUIIntrospect */, ); productName = "Share Ext"; productReference = F7CE8AFB1DC1F8D8009CAE48 /* Share.appex */; @@ -2868,6 +2879,7 @@ F783030C28B4C59A00B84583 /* SwiftEntryKit */, F783034328B5142B00B84583 /* NextcloudKit */, F787AC0A298BCB540001BB00 /* SVGKitSwift */, + F3077B7D2ABDA0FD0013BA13 /* SwiftUIIntrospect */, ); productName = DashboardWidgetExtension; productReference = F7346E1028B0EF5B006CE2D2 /* Widget.appex */; @@ -2891,6 +2903,7 @@ F710FC83277B7D3500AA9FBF /* RealmSwift */, F7EBCDD0277B820D00A4EF67 /* UICKeyChainStore */, F72AD71228C24BCC006CB92D /* NextcloudKit */, + F3077B832ABDA1190013BA13 /* SwiftUIIntrospect */, ); productName = "File Provider Extension"; productReference = F771E3D020E2392D00AFB62D /* File Provider Extension.appex */; @@ -2948,9 +2961,7 @@ F3C0FB6F2AB06AB9007E7E30 /* VisibilityTrackingScrollView */, F3EFBF5E2AB2100E00B5724C /* Shimmer */, F30A6BEF2AB4AAB700148857 /* Refreshable */, - F30A6C062ABCA34200148857 /* SwiftUIIntrospect */, - F30A6C082ABCA34200148857 /* SwiftUIIntrospect-Dynamic */, - F30A6C0A2ABCA34200148857 /* SwiftUIIntrospect-Static */, + F3077B7B2ABDA06A0013BA13 /* SwiftUIIntrospect */, ); productName = "Crypto Cloud"; productReference = F7CE8AFA1DC1F8D8009CAE48 /* Nextcloud.app */; @@ -2973,6 +2984,7 @@ F7A8D72328F1771B008BBE1C /* NextcloudKit */, F7A8D72728F17728008BBE1C /* RealmSwift */, F7A8D72D28F17764008BBE1C /* UICKeyChainStore */, + F3077B7F2ABDA10B0013BA13 /* SwiftUIIntrospect */, ); productName = WidgetDashboardIntentHandler; productReference = F7C9739028F17131002C43E2 /* WidgetDashboardIntentHandler.appex */; @@ -3129,7 +3141,7 @@ F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */, F3EFBF5D2AB20B9100B5724C /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */, F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */, - F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, + F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, ); productRefGroup = F7F67B9F1A24D27800EE80DA; projectDirPath = ""; @@ -4979,14 +4991,6 @@ version = 0.2.0; }; }; - F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.0; - }; - }; F31F694B2A2F6EFA00162F76 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing.git"; @@ -5011,6 +5015,14 @@ version = 1.4.0; }; }; + F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/siteline/SwiftUI-Introspect"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; F3C0FB6E2AB06AB9007E7E30 /* XCRemoteSwiftPackageReference "VisibilityTrackingScrollView" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/elegantchaos/VisibilityTrackingScrollView"; @@ -5234,25 +5246,45 @@ package = F76DA964277B76F10082465B /* XCRemoteSwiftPackageReference "UICKeyChainStore" */; productName = UICKeyChainStore; }; - F30A6BEF2AB4AAB700148857 /* Refreshable */ = { + F3077B7B2ABDA06A0013BA13 /* SwiftUIIntrospect */ = { isa = XCSwiftPackageProductDependency; - package = F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */; - productName = Refreshable; + package = F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; }; - F30A6C062ABCA34200148857 /* SwiftUIIntrospect */ = { + F3077B7D2ABDA0FD0013BA13 /* SwiftUIIntrospect */ = { isa = XCSwiftPackageProductDependency; - package = F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + package = F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; productName = SwiftUIIntrospect; }; - F30A6C082ABCA34200148857 /* SwiftUIIntrospect-Dynamic */ = { + F3077B7F2ABDA10B0013BA13 /* SwiftUIIntrospect */ = { isa = XCSwiftPackageProductDependency; - package = F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = "SwiftUIIntrospect-Dynamic"; + package = F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; + }; + F3077B812ABDA1120013BA13 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + package = F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; }; - F30A6C0A2ABCA34200148857 /* SwiftUIIntrospect-Static */ = { + F3077B832ABDA1190013BA13 /* SwiftUIIntrospect */ = { isa = XCSwiftPackageProductDependency; - package = F30A6C052ABCA34200148857 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; - productName = "SwiftUIIntrospect-Static"; + package = F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; + }; + F3077B852ABDA1200013BA13 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + package = F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; + }; + F3077B872ABDA1240013BA13 /* SwiftUIIntrospect */ = { + isa = XCSwiftPackageProductDependency; + package = F3718CC02ABD9A76009BC4C2 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */; + productName = SwiftUIIntrospect; + }; + F30A6BEF2AB4AAB700148857 /* Refreshable */ = { + isa = XCSwiftPackageProductDependency; + package = F30A6BEE2AB4AAB700148857 /* XCRemoteSwiftPackageReference "Refreshable" */; + productName = Refreshable; }; F30A96332A2DFCD000D7BCFE /* Realm */ = { isa = XCSwiftPackageProductDependency; diff --git a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift index 303daa231c..ac9367dded 100644 --- a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift +++ b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift @@ -33,3 +33,27 @@ extension SwiftUI.View { self.modifier(DeviceOrientationViewModifier(action: action)) } } + +struct BlinkViewModifier: ViewModifier { + + let duration: Double + @State private var blinking: Bool = false + + func body(content: Content) -> some View { + withAnimation(.easeOut(duration: duration).repeatForever(), { + content + .opacity(blinking ? 0 : 1) + .onAppear { + withAnimation { + blinking = true + } + } + }) + } +} + +extension View { + func blinking(duration: Double = 0.75) -> some View { + modifier(BlinkViewModifier(duration: duration)) + } +} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index a347d02ba7..8e01481600 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -31,6 +31,12 @@ struct NCMediaNew: View { ZStack(alignment: .top) { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { + if vm.firstTimeLoadingNewMedia { + Text("Checking for new photos...").font(.system(size: 13)) + .frame(maxWidth: .infinity) + .blinking() + } + ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) { tappedThumbnail, isSelected in @@ -76,10 +82,10 @@ struct NCMediaNew: View { await vm.onPullToRefresh() } // Not possible to move the refresh control view via SwiftUI, so we have to introspect the internal UIKit views to move it. - // TODO: Maybe .contentMargins() will resolve this but it's iOS 17+ + // TODO: Maybe .contespontMargins() will resolve this but it's iOS 17+ .introspect(.scrollView, on: .iOS(.v15...)) { scrollView in scrollView.refreshControl?.translatesAutoresizingMaskIntoConstraints = false - scrollView.refreshControl?.topAnchor.constraint(equalTo: parent.view.topAnchor, constant: 120).isActive = true + scrollView.refreshControl?.topAnchor.constraint(equalTo: scrollView.superview!.topAnchor, constant: 120).isActive = true scrollView.refreshControl?.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true } .coordinateSpace(name: "scroll") @@ -144,13 +150,6 @@ struct NCMediaNew: View { Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") }) } - -// Picker("Sorting options", selection: $vm.sortType) { -// Label(NSLocalizedString("_media_by_modified_date_", comment: ""), systemImage: "circle.grid.cross.up.fill").tag(SortType.modifiedDate) -// Label(NSLocalizedString("_media_by_created_date_", comment: ""), systemImage: "circle.grid.cross.down.fill").tag(SortType.creationDate) -// Label(NSLocalizedString("_media_by_upload_date_", comment: ""), systemImage: "circle.grid.cross.right.fill").tag(SortType.uploadDate) -// } -// .pickerStyle(.menu) } label: { ToolbarCircularButton(imageSystemName: "ellipsis", toolbarItemsColor: $toolbarItemsColor) } @@ -212,6 +211,7 @@ struct NCMediaNew_Previews: PreviewProvider { ], configure: { _ in NCMediaNew() + .environmentObject(NCMediaUIKitWrapper()) }) } } diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 5e6b2dfd0a..79828bf9ed 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -9,10 +9,6 @@ import NextcloudKit import Combine -//enum SortType: String { -// case modifiedDate = "date", creationDate = "creationDate", uploadDate = "uploadDate" -//} - @MainActor class NCMediaViewModel: ObservableObject { @Published private(set) var metadatas: [tableMetadata] = [] @Published internal var selectedMetadatas: [tableMetadata] = [] @@ -29,11 +25,10 @@ import Combine @Published internal var filterClassTypeImage = false @Published internal var filterClassTypeVideo = false -// @Published internal var sortType: SortType = SortType(rawValue: CCUtility.getMediaSortDate()) ?? .modifiedDate - private var cancellables: Set = [] - internal var needsLoadingMoreItems = true + @Published internal var needsLoadingMoreItems = true + @Published internal var firstTimeLoadingNewMedia = false init() { NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) @@ -49,11 +44,6 @@ import Combine $filterClassTypeImage.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) $filterClassTypeVideo.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) -// $sortType.sink { sortType in -// CCUtility.setMediaSortDate(sortType.rawValue) -// self.loadMediaFromDB() -// } -// .store(in: &cancellables) } deinit { @@ -62,6 +52,7 @@ import Combine NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCopyFile), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterRenameFile), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterUploadedFile), object: nil) + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil) } private func queryDB(isForced: Bool = false) { @@ -135,6 +126,10 @@ import Combine } } } + + func chunkMetadatas(into columns: Int) -> [[tableMetadata]] { + return metadatas.chunked(into: columns) + } } // MARK: Notifications @@ -245,6 +240,10 @@ extension NCMediaViewModel { } else if error != .success { NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Media search old media error code \(error.errorCode) " + error.errorDescription) } + + DispatchQueue.main.async { + self.needsLoadingMoreItems = false + } } } @@ -271,6 +270,8 @@ extension NCMediaViewModel { let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) + firstTimeLoadingNewMedia = true + return await withCheckedContinuation { continuation in NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in @@ -290,6 +291,10 @@ extension NCMediaViewModel { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) } + DispatchQueue.main.async { + self.firstTimeLoadingNewMedia = false + } + continuation.resume() } } From b1120d7ebf9bdaaf1631c6f4b233cc10eecbbad9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 22 Sep 2023 14:43:13 +0200 Subject: [PATCH 093/103] WIP Signed-off-by: Milen Pivchev --- .../Extensions/SwiftUIView+Extensions.swift | 24 -- iOSClient/Media/NCMediaNew.swift | 254 +++++++++--------- iOSClient/Media/NCMediaViewModel.swift | 7 - 3 files changed, 124 insertions(+), 161 deletions(-) diff --git a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift index ac9367dded..303daa231c 100644 --- a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift +++ b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift @@ -33,27 +33,3 @@ extension SwiftUI.View { self.modifier(DeviceOrientationViewModifier(action: action)) } } - -struct BlinkViewModifier: ViewModifier { - - let duration: Double - @State private var blinking: Bool = false - - func body(content: Content) -> some View { - withAnimation(.easeOut(duration: duration).repeatForever(), { - content - .opacity(blinking ? 0 : 1) - .onAppear { - withAnimation { - blinking = true - } - } - }) - } -} - -extension View { - func blinking(duration: Double = 0.75) -> some View { - modifier(BlinkViewModifier(duration: duration)) - } -} diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 8e01481600..0b429cab4b 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -27,151 +27,145 @@ struct NCMediaNew: View { @State private var showDeleteConfirmation = false var body: some View { - GeometryReader { outerProxy in - ZStack(alignment: .top) { - VisibilityTrackingScrollView(action: cellVisibilityDidChange) { - LazyVStack(alignment: .leading, spacing: 2) { - if vm.firstTimeLoadingNewMedia { - Text("Checking for new photos...").font(.system(size: 13)) - .frame(maxWidth: .infinity) - .blinking() - } - - ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) { tappedThumbnail, isSelected in - - if vm.isInSelectMode, isSelected { - vm.selectedMetadatas.append(tappedThumbnail.metadata) - } else { - vm.selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) - } - - if !vm.isInSelectMode { - let selectedMetadata = tappedThumbnail.metadata - vm.onCellTapped(metadata: selectedMetadata) - NCViewer.shared.view(viewController: parent, metadata: selectedMetadata, metadatas: vm.metadatas, imageIcon: tappedThumbnail.image) - } + GeometryReader { outerProxy in + ZStack(alignment: .top) { + VisibilityTrackingScrollView(action: cellVisibilityDidChange) { + LazyVStack(alignment: .leading, spacing: 2) { + ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) { tappedThumbnail, isSelected in + + if vm.isInSelectMode, isSelected { + vm.selectedMetadatas.append(tappedThumbnail.metadata) + } else { + vm.selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) } - } - if vm.needsLoadingMoreItems { - ProgressView() - .frame(maxWidth: .infinity) - .onAppear { vm.loadMoreItems() } - .padding(.top, 10) + if !vm.isInSelectMode { + let selectedMetadata = tappedThumbnail.metadata + vm.onCellTapped(metadata: selectedMetadata) + NCViewer.shared.view(viewController: parent, metadata: selectedMetadata, metadatas: vm.metadatas, imageIcon: tappedThumbnail.image) + } } } - .padding(.top, 70) - .padding(.bottom, 40) - .background(GeometryReader { geometry in - Color.clear - .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) - }) - .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in - let isScrolledToTop = value.y >= -10 - withAnimation(.default) { - titleColor = isScrolledToTop ? Color.primary : .white - toolbarItemsColor = isScrolledToTop ? .blue : .white - toolbarColors = isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)] - } + if vm.needsLoadingMoreItems { + ProgressView() + .frame(maxWidth: .infinity) + .onAppear { vm.loadMoreItems() } + .padding(.top, 10) } - - } - .refreshable { - await vm.onPullToRefresh() } - // Not possible to move the refresh control view via SwiftUI, so we have to introspect the internal UIKit views to move it. - // TODO: Maybe .contespontMargins() will resolve this but it's iOS 17+ - .introspect(.scrollView, on: .iOS(.v15...)) { scrollView in - scrollView.refreshControl?.translatesAutoresizingMaskIntoConstraints = false - scrollView.refreshControl?.topAnchor.constraint(equalTo: scrollView.superview!.topAnchor, constant: 120).isActive = true - scrollView.refreshControl?.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true + .padding(.top, 70) + .padding(.bottom, 40) + .background(GeometryReader { geometry in + Color.clear + .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) + }) + .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in + let isScrolledToTop = value.y >= -10 + + withAnimation(.default) { + titleColor = isScrolledToTop ? Color.primary : .white + toolbarItemsColor = isScrolledToTop ? .blue : .white + toolbarColors = isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)] + } } - .coordinateSpace(name: "scroll") - - // Toolbar - - HStack(content: { - HStack { - Text(title) - .font(.system(size: 20, weight: .bold)) - .foregroundStyle(titleColor) - - Spacer() - - Button(action: { - vm.isInSelectMode.toggle() - }, label: { - Text(NSLocalizedString(vm.isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) - .foregroundStyle(toolbarItemsColor) - }) - .padding(.horizontal, 6) - .padding(.vertical, 3) - .background(.ultraThinMaterial) - .cornerRadius(.infinity) - - if vm.isInSelectMode, !vm.selectedMetadatas.isEmpty { - ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) - .onTapGesture { - showDeleteConfirmation = true - } - .confirmationDialog("", isPresented: $showDeleteConfirmation) { - Button("Delete selected media", role: .destructive) { - vm.deleteSelectedMetadata() - } - } - } - Menu { - Section { - Button(action: { - vm.filterClassTypeImage = !vm.filterClassTypeImage - vm.filterClassTypeVideo = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") - }) - Button(action: { - vm.filterClassTypeVideo = !vm.filterClassTypeVideo - vm.filterClassTypeImage = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") - }) - } + } + .refreshable { + await vm.onPullToRefresh() + } + // Not possible to move the refresh control view via SwiftUI, so we have to introspect the internal UIKit views to move it. + // TODO: Maybe .contespontMargins() will resolve this but it's iOS 17+ + .introspect(.scrollView, on: .iOS(.v15...)) { scrollView in + scrollView.refreshControl?.translatesAutoresizingMaskIntoConstraints = false + scrollView.refreshControl?.topAnchor.constraint(equalTo: scrollView.superview!.topAnchor, constant: 120).isActive = true + scrollView.refreshControl?.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true + } + .coordinateSpace(name: "scroll") + + // Toolbar + + HStack(content: { + HStack { + Text(title) + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(titleColor) + + Spacer() - Section { - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") - }) - Button(action: {}, label: { - Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") - }) + Button(action: { + vm.isInSelectMode.toggle() + }, label: { + Text(NSLocalizedString(vm.isInSelectMode ? "_cancel_" : "_select_", comment: "")).font(.system(size: 14)) + .foregroundStyle(toolbarItemsColor) + }) + .padding(.horizontal, 6) + .padding(.vertical, 3) + .background(.ultraThinMaterial) + .cornerRadius(.infinity) + + if vm.isInSelectMode, !vm.selectedMetadatas.isEmpty { + ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) + .onTapGesture { + showDeleteConfirmation = true + } + .confirmationDialog("", isPresented: $showDeleteConfirmation) { + Button("Delete selected media", role: .destructive) { + vm.deleteSelectedMetadata() + } } - } label: { - ToolbarCircularButton(imageSystemName: "ellipsis", toolbarItemsColor: $toolbarItemsColor) + } + + Menu { + Section { + Button(action: { + vm.filterClassTypeImage = !vm.filterClassTypeImage + vm.filterClassTypeVideo = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") + }) + Button(action: { + vm.filterClassTypeVideo = !vm.filterClassTypeVideo + vm.filterClassTypeImage = false + }, label: { + Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + }) } + + Section { + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + Button(action: {}, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } + } label: { + ToolbarCircularButton(imageSystemName: "ellipsis", toolbarItemsColor: $toolbarItemsColor) } - }) - .frame(maxWidth: .infinity) - .padding([.horizontal, .top], 10) - .padding(.bottom, 20) - .background(LinearGradient(gradient: Gradient(colors: toolbarColors), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) - } - } - .onRotate { orientation in - if orientation.isLandscapeHardCheck { - columns = 6 - } else { - columns = 2 - } + } + }) + .frame(maxWidth: .infinity) + .padding([.horizontal, .top], 10) + .padding(.bottom, 20) + .background(LinearGradient(gradient: Gradient(colors: toolbarColors), startPoint: .top, endPoint: .bottom).edgesIgnoringSafeArea(.top)) } - .onAppear { vm.loadMediaFromDB() } - .onChange(of: vm.isInSelectMode) { newValue in - if newValue == false { vm.selectedMetadatas.removeAll() } + } + .onRotate { orientation in + if orientation.isLandscapeHardCheck { + columns = 6 + } else { + columns = 2 } + } + .onAppear { vm.loadMediaFromDB() } + .onChange(of: vm.isInSelectMode) { newValue in + if newValue == false { vm.selectedMetadatas.removeAll() } + } } func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 79828bf9ed..3e39a8e34f 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -28,7 +28,6 @@ import Combine private var cancellables: Set = [] @Published internal var needsLoadingMoreItems = true - @Published internal var firstTimeLoadingNewMedia = false init() { NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) @@ -270,8 +269,6 @@ extension NCMediaViewModel { let options = NKRequestOptions(timeout: 300, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) - firstTimeLoadingNewMedia = true - return await withCheckedContinuation { continuation in NextcloudKit.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), options: options) { account, files, _, error in @@ -291,10 +288,6 @@ extension NCMediaViewModel { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(error.errorCode) " + error.errorDescription) } - DispatchQueue.main.async { - self.firstTimeLoadingNewMedia = false - } - continuation.resume() } } From 5137790ab12ea2aecc5fed2472599796cb1da361 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 25 Sep 2023 14:37:06 +0200 Subject: [PATCH 094/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 23 +++- iOSClient/Media/NCMediaNew.swift | 102 ++++++++++++++---- iOSClient/Media/NCMediaRow.swift | 3 +- iOSClient/Media/NCMediaRowViewModel.swift | 4 +- iOSClient/Media/NCMediaViewModel.swift | 63 ++++++++--- .../en.lproj/Localizable.strings | 7 +- 6 files changed, 155 insertions(+), 47 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index ccbf5b48da..8fcdec0420 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -11,25 +11,38 @@ import VisibilityTrackingScrollView import Shimmer import NextcloudKit +enum ContextMenuSelection { + case detail, openIn +} + struct NCMediaCell: View { let thumbnail: ScaledThumbnail let shrinkRatio: CGFloat @Binding var isInSelectMode: Bool @State private var isSelected = false let onSelected: (ScaledThumbnail, Bool) -> Void + let onContextMenuItemSelected: (ScaledThumbnail, ContextMenuSelection) -> Void var body: some View { let image = Image(uiImage: thumbnail.image) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") .contextMenu(ContextMenu(menuItems: { - Text("Menu Item 1") - Text("Menu Item 2") - Text("Menu Item 3") + Button { + onContextMenuItemSelected(thumbnail, .detail) + } label: { + Label("Details", systemImage: "info") + } + + Button { + onContextMenuItemSelected(thumbnail, .openIn) + } label: { + Label("Open in", systemImage: "info") + } })) ZStack(alignment: .center) { - if thumbnail.isDefaultImage { + if thumbnail.isPlaceholderImage { image .foregroundColor(Color(uiColor: .systemGray4)) .scaledToFit() @@ -40,7 +53,7 @@ struct NCMediaCell: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .overlay(alignment: .bottomLeading) { - if thumbnail.metadata.isVideo, !thumbnail.isDefaultImage { + if thumbnail.metadata.isVideo, !thumbnail.isPlaceholderImage { Image(systemName: "play.fill") .resizable() .foregroundColor(Color(uiColor: .white)) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 0b429cab4b..28bb1d7def 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -32,8 +32,10 @@ struct NCMediaNew: View { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) { tappedThumbnail, isSelected in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) + { tappedThumbnail, isSelected in + //TODO: Put in VM if vm.isInSelectMode, isSelected { vm.selectedMetadatas.append(tappedThumbnail.metadata) } else { @@ -45,14 +47,23 @@ struct NCMediaNew: View { vm.onCellTapped(metadata: selectedMetadata) NCViewer.shared.view(viewController: parent, metadata: selectedMetadata, metadatas: vm.metadatas, imageIcon: tappedThumbnail.image) } + } onCellContextMenuItemSelected: { thumbnail, selection in + let selectedMetadata = thumbnail.metadata + + switch selection { + case .detail: + NCActionCenter.shared.openShare(viewController: parent, metadata: selectedMetadata, page: .activity) + case .openIn: + vm.openIn(metadata: selectedMetadata) + } } - } - if vm.needsLoadingMoreItems { - ProgressView() - .frame(maxWidth: .infinity) - .onAppear { vm.loadMoreItems() } - .padding(.top, 10) + if vm.needsLoadingMoreItems { + ProgressView() + .frame(maxWidth: .infinity) + .onAppear { vm.loadMoreItems() } + .padding(.top, 10) + } } } .padding(.top, 70) @@ -119,21 +130,70 @@ struct NCMediaNew: View { Menu { Section { - Button(action: { - vm.filterClassTypeImage = !vm.filterClassTypeImage - vm.filterClassTypeVideo = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") - }) - Button(action: { - vm.filterClassTypeVideo = !vm.filterClassTypeVideo - vm.filterClassTypeImage = false - }, label: { - Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") - }) - Button(action: {}, label: { +// Button { +// vm.filterClassTypeImage = !vm.filterClassTypeImage +// vm.filterClassTypeVideo = false +// } label: { +// HStack { +// Image(systemName: "photo.fill") +// +// Spacer() +// Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") +// } +// } + + Picker("View options", selection: $vm.filter) { + Label(NSLocalizedString("_media_viewimage_show_", comment: ""), systemImage: "photo.fill").tag(Filter.onlyPhotos) + + Label(NSLocalizedString("_media_viewvideo_show_", comment: ""), systemImage: "video.fill").tag(Filter.onlyVideos) + + Text(NSLocalizedString("_media_show_all_", comment: "")).tag(Filter.all) + }.pickerStyle(.menu) + +// Toggle(isOn: $vm.showImages) { +// Label(NSLocalizedString("_media_viewimage_show_", comment: ""), systemImage: "photo.fill") +// } +// .onChange(of: vm.showImages) { _ in +// vm.showVideo = true +// } +// .onTapGesture { +//// vm.showImages = !vm.showImages +// vm.showVideo = true +// } + +// Toggle(isOn: $vm.showVideo) { +// Label(NSLocalizedString("_media_viewvideo_show_", comment: ""), systemImage: "video.fill") +// } +// .onChange(of: vm.showVideo) { _ in +// vm.showImages = true +// } + +// .onTapGesture { +//// vm.showVideo = !vm.showVideo +// vm.showImages = true +// } + +// Button { +// vm.filterClassTypeVideo = !vm.filterClassTypeVideo +// vm.filterClassTypeImage = false +// } label: { +// Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") +// } + + Button { + guard let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, + let viewController = navigationController.topViewController as? NCSelect + else { return } + +// viewController.delegate = self + viewController.typeOfCommandView = .select + viewController.type = "mediaFolder" +// viewController.selectIndexPath = self.selectIndexPath + + parent.present(navigationController, animated: true, completion: nil) + } label: { Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") - }) + } } Section { diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 8b72a9e88f..98f040dd7f 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -15,6 +15,7 @@ struct NCMediaRow: View { let geometryProxy: GeometryProxy @Binding var isInSelectMode: Bool let onCellSelected: (ScaledThumbnail, Bool) -> Void + let onCellContextMenuItemSelected: (ScaledThumbnail, ContextMenuSelection) -> Void @StateObject private var vm = NCMediaRowViewModel() private let spacing: CGFloat = 2 @@ -26,7 +27,7 @@ struct NCMediaRow: View { } } else { ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onSelected: onCellSelected) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onSelected: onCellSelected, onContextMenuItemSelected: onCellContextMenuItemSelected) } } } diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift index 8b069c9a74..d604d1da63 100644 --- a/iOSClient/Media/NCMediaRowViewModel.swift +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -16,7 +16,7 @@ struct RowData { struct ScaledThumbnail: Hashable { let image: UIImage - var isDefaultImage = false + var isPlaceholderImage = false var scaledSize: CGSize = .zero let metadata: tableMetadata @@ -81,7 +81,7 @@ struct ScaledThumbnail: Hashable { NCManageDatabase.shared.setMetadataEtagResource(ocId: metadata.ocId, etagResource: etag) thumbnails.append(ScaledThumbnail(image: image, metadata: metadata)) } else { - thumbnails.append(ScaledThumbnail(image: UIImage(systemName: metadata.isVideo ? "video.fill" : "photo.fill")!.withRenderingMode(.alwaysTemplate), isDefaultImage: true, metadata: metadata)) + thumbnails.append(ScaledThumbnail(image: UIImage(systemName: metadata.isVideo ? "video.fill" : "photo.fill")!.withRenderingMode(.alwaysTemplate), isPlaceholderImage: true, metadata: metadata)) } DispatchQueue.main.async { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 3e39a8e34f..afa28adb13 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -22,12 +22,13 @@ import Combine private var predicate: NSPredicate? private let appDelegate = UIApplication.shared.delegate as? AppDelegate - @Published internal var filterClassTypeImage = false - @Published internal var filterClassTypeVideo = false +// @Published internal var showImages = true +// @Published internal var showVideo = true private var cancellables: Set = [] @Published internal var needsLoadingMoreItems = true + @Published internal var filter = Filter.all init() { NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil) @@ -41,8 +42,19 @@ import Combine await loadNewMedia() } - $filterClassTypeImage.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) - $filterClassTypeVideo.sink { _ in self.loadMediaFromDB() }.store(in: &cancellables) + $filter + .dropFirst() + .sink { filter in + switch filter { + case .all: + self.loadMediaFromDB(showPhotos: true, showVideos: true) + case .onlyPhotos: + self.loadMediaFromDB(showPhotos: true, showVideos: false) + case .onlyVideos: + self.loadMediaFromDB(showPhotos: false, showVideos: true) + } + } + .store(in: &cancellables) } deinit { @@ -54,7 +66,7 @@ import Combine NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil) } - private func queryDB(isForced: Bool = false) { + private func queryDB(isForced: Bool = false, showPhotos: Bool = true, showVideos: Bool = true) { guard let appDelegate else { return } livePhoto = CCUtility.getLivePhoto() @@ -67,12 +79,12 @@ import Combine predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == %@ OR classFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue, NKCommon.TypeClassFile.video.rawValue) - if filterClassTypeImage { - predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) - } else if filterClassTypeVideo { - predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) - } else { + if showPhotos, showVideos { predicate = predicateDefault + } else if showPhotos { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue) + } else if showVideos { + predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue) } guard let predicate = predicate else { return } @@ -126,8 +138,27 @@ import Combine } } - func chunkMetadatas(into columns: Int) -> [[tableMetadata]] { - return metadatas.chunked(into: columns) + func openIn(metadata: tableMetadata) { + if CCUtility.fileProviderStorageExists(metadata) { + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorOpenIn, "error": NKError(), "account": metadata.account]) + } else { +// hud.show(in: viewController.view) + NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorOpenIn, notificationCenterProgressTask: false) +// { request in +// downloadRequest = request +// } progressHandler: { progress in +// hud.progress = Float(progress.fractionCompleted) +// } completion: + { afError, error in + if error == .success || afError?.isExplicitlyCancelledError ?? false { +// hud.dismiss() + } else { +// hud.indicatorView = JGProgressHUDErrorIndicatorView() +// hud.textLabel.text = error.description +// hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond) + } + } + } } } @@ -190,7 +221,7 @@ extension NCMediaViewModel { // MARK: - Load media extension NCMediaViewModel { - func loadMediaFromDB() { + func loadMediaFromDB(showPhotos: Bool = true, showVideos: Bool = true) { guard let appDelegate, !appDelegate.account.isEmpty else { return } if account != appDelegate.account { @@ -198,7 +229,7 @@ extension NCMediaViewModel { account = appDelegate.account } - self.queryDB(isForced: true) + self.queryDB(isForced: true, showImages: showPhotos, showVideos: showVideos) } private func loadOldMedia(value: Int = -30, limit: Int = 300) { @@ -293,3 +324,7 @@ extension NCMediaViewModel { } } } + +enum Filter { + case onlyPhotos, onlyVideos, all +} diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index be05e6d9fc..84c976b44e 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -585,10 +585,9 @@ "_status_uploading_" = "Uploading"; "_status_upload_error_" = "Error, waiting for upload"; "_select_media_folder_" = "Select the \"Media\" folder"; -"_media_viewimage_show_" = "Show images"; -"_media_viewimage_hide_" = "Hide images"; -"_media_viewvideo_show_" = "Show video"; -"_media_viewvideo_hide_" = "Hide video"; +"_media_viewimage_show_" = "Show only images"; +"_media_viewvideo_show_" = "Show only videos"; +"_media_show_all_" = "Show both"; "_media_by_created_date_" = "Sort by created date"; "_media_by_upload_date_" = "Sort by upload date"; "_media_by_modified_date_" = "Sort by modified date"; From 2c3368e97184341cbb0622813e40a618b91c90d9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 25 Sep 2023 16:31:58 +0200 Subject: [PATCH 095/103] Enable select media folder Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 70 ++++--------------- iOSClient/Media/NCMediaViewModel.swift | 20 ++++-- .../Media/UIWrapper/NCMediaUIKitWrapper.swift | 1 - .../en.lproj/Localizable.strings | 1 + 4 files changed, 32 insertions(+), 60 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 28bb1d7def..1052067133 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -81,7 +81,6 @@ struct NCMediaNew: View { toolbarColors = isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)] } } - } .refreshable { await vm.onPullToRefresh() @@ -130,19 +129,7 @@ struct NCMediaNew: View { Menu { Section { -// Button { -// vm.filterClassTypeImage = !vm.filterClassTypeImage -// vm.filterClassTypeVideo = false -// } label: { -// HStack { -// Image(systemName: "photo.fill") -// -// Spacer() -// Label(NSLocalizedString(vm.filterClassTypeImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""), systemImage: "photo.fill") -// } -// } - - Picker("View options", selection: $vm.filter) { + Picker(NSLocalizedString("_media_view_options_", comment: ""), selection: $vm.filter) { Label(NSLocalizedString("_media_viewimage_show_", comment: ""), systemImage: "photo.fill").tag(Filter.onlyPhotos) Label(NSLocalizedString("_media_viewvideo_show_", comment: ""), systemImage: "video.fill").tag(Filter.onlyVideos) @@ -150,47 +137,8 @@ struct NCMediaNew: View { Text(NSLocalizedString("_media_show_all_", comment: "")).tag(Filter.all) }.pickerStyle(.menu) -// Toggle(isOn: $vm.showImages) { -// Label(NSLocalizedString("_media_viewimage_show_", comment: ""), systemImage: "photo.fill") -// } -// .onChange(of: vm.showImages) { _ in -// vm.showVideo = true -// } -// .onTapGesture { -//// vm.showImages = !vm.showImages -// vm.showVideo = true -// } - -// Toggle(isOn: $vm.showVideo) { -// Label(NSLocalizedString("_media_viewvideo_show_", comment: ""), systemImage: "video.fill") -// } -// .onChange(of: vm.showVideo) { _ in -// vm.showImages = true -// } - -// .onTapGesture { -//// vm.showVideo = !vm.showVideo -// vm.showImages = true -// } - -// Button { -// vm.filterClassTypeVideo = !vm.filterClassTypeVideo -// vm.filterClassTypeImage = false -// } label: { -// Label(NSLocalizedString(vm.filterClassTypeVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""), systemImage: "video.fill") -// } - Button { - guard let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, - let viewController = navigationController.topViewController as? NCSelect - else { return } - -// viewController.delegate = self - viewController.typeOfCommandView = .select - viewController.type = "mediaFolder" -// viewController.selectIndexPath = self.selectIndexPath - - parent.present(navigationController, animated: true, completion: nil) + selectMediaFolder() } label: { Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") } @@ -228,13 +176,25 @@ struct NCMediaNew: View { } } - func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { + private func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { DispatchQueue.main.async { if let date = tracker.topVisibleView, !date.isEmpty { title = date } } } + + private func selectMediaFolder() { + guard let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, + let viewController = navigationController.topViewController as? NCSelect + else { return } + + viewController.delegate = vm + viewController.typeOfCommandView = .select + viewController.type = "mediaFolder" + + parent.present(navigationController, animated: true, completion: nil) + } } struct ToolbarCircularButton: View { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index afa28adb13..50a650acb0 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -22,9 +22,6 @@ import Combine private var predicate: NSPredicate? private let appDelegate = UIApplication.shared.delegate as? AppDelegate -// @Published internal var showImages = true -// @Published internal var showVideo = true - private var cancellables: Set = [] @Published internal var needsLoadingMoreItems = true @@ -229,7 +226,7 @@ extension NCMediaViewModel { account = appDelegate.account } - self.queryDB(isForced: true, showImages: showPhotos, showVideos: showVideos) + self.queryDB(isForced: true, showPhotos: showPhotos, showVideos: showVideos) } private func loadOldMedia(value: Int = -30, limit: Int = 300) { @@ -325,6 +322,21 @@ extension NCMediaViewModel { } } +extension NCMediaViewModel: NCSelectDelegate { + func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) { + guard let serverUrl = serverUrl, let appDelegate else { return } + + let path = CCUtility.returnPathfromServerUrl(serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId, account: appDelegate.account) ?? "" + NCManageDatabase.shared.setAccountMediaPath(path, account: appDelegate.account) + + self.loadMediaFromDB() + + Task { + await loadNewMedia() + } + } +} + enum Filter { case onlyPhotos, onlyVideos, all } diff --git a/iOSClient/Media/UIWrapper/NCMediaUIKitWrapper.swift b/iOSClient/Media/UIWrapper/NCMediaUIKitWrapper.swift index 41344f0f21..a2ce81d20b 100644 --- a/iOSClient/Media/UIWrapper/NCMediaUIKitWrapper.swift +++ b/iOSClient/Media/UIWrapper/NCMediaUIKitWrapper.swift @@ -31,7 +31,6 @@ class NCMediaUIKitWrapper: UIViewController, ObservableObject { view.addSubview(controller.view) controller.didMove(toParent: self) - NSLayoutConstraint.activate([ controller.view.widthAnchor.constraint(equalTo: view.widthAnchor), controller.view.heightAnchor.constraint(equalTo: view.heightAnchor), diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 84c976b44e..9eebb73516 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -588,6 +588,7 @@ "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only videos"; "_media_show_all_" = "Show both"; +"_media_view_options_" = "View options"; "_media_by_created_date_" = "Sort by created date"; "_media_by_upload_date_" = "Sort by upload date"; "_media_by_modified_date_" = "Sort by modified date"; From 3d2b45a1e0fe52a91cda42a5dd8cf5ba54d2f290 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Sep 2023 11:09:01 +0200 Subject: [PATCH 096/103] Add other context options Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 53 ++++++++++-- iOSClient/Media/NCMediaNew.swift | 15 +++- iOSClient/Media/NCMediaRow.swift | 2 +- iOSClient/Media/NCMediaViewModel.swift | 85 ++++++++++++++++--- .../en.lproj/Localizable.strings | 2 +- 5 files changed, 136 insertions(+), 21 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 8fcdec0420..9e719d5c48 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -12,7 +12,7 @@ import Shimmer import NextcloudKit enum ContextMenuSelection { - case detail, openIn + case addToFavorites, detail, openIn, saveToPhotos, viewInFolder, modify, delete } struct NCMediaCell: View { @@ -23,23 +23,65 @@ struct NCMediaCell: View { let onSelected: (ScaledThumbnail, Bool) -> Void let onContextMenuItemSelected: (ScaledThumbnail, ContextMenuSelection) -> Void + @State var isFavorite: Bool + + @State private var showDeleteConfirmation = false + var body: some View { let image = Image(uiImage: thumbnail.image) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") - .contextMenu(ContextMenu(menuItems: { + .contextMenu(menuItems: { + Button { + isFavorite.toggle() + onContextMenuItemSelected(thumbnail, .addToFavorites) + } label: { + Label(isFavorite ? + NSLocalizedString("_remove_favorites_", comment: "") : + NSLocalizedString("_add_favorites_", comment: ""), systemImage: "star.fill") + } + Button { onContextMenuItemSelected(thumbnail, .detail) } label: { - Label("Details", systemImage: "info") + Label(NSLocalizedString("_details_", comment: ""), systemImage: "info") } Button { onContextMenuItemSelected(thumbnail, .openIn) } label: { - Label("Open in", systemImage: "info") + Label(NSLocalizedString("_open_in_", comment: ""), systemImage: "info") + } + + Button { + onContextMenuItemSelected(thumbnail, .saveToPhotos) + } label: { + Label(NSLocalizedString("_save_selected_files_", comment: ""), systemImage: "square.and.arrow.down") + } + + Button { + onContextMenuItemSelected(thumbnail, .viewInFolder) + } label: { + Label(NSLocalizedString("_view_in_folder_", comment: ""), systemImage: "folder.fill") + } + + Button { + onContextMenuItemSelected(thumbnail, .modify) + } label: { + Label(NSLocalizedString("_modify_", comment: ""), systemImage: "pencil.tip.crop.circle") } - })) + + Button(role: .destructive) { + showDeleteConfirmation = true + } label: { + Label(NSLocalizedString("_delete_file_", comment: ""), systemImage: "pencil.tip.crop.circle") + } + }) + .confirmationDialog("", isPresented: $showDeleteConfirmation) { + Button(NSLocalizedString("_delete_file_", comment: ""), role: .destructive) { + onContextMenuItemSelected(thumbnail, .delete) + } + } ZStack(alignment: .center) { if thumbnail.isPlaceholderImage { @@ -88,6 +130,7 @@ struct NCMediaCell: View { .onChange(of: isInSelectMode) { newValue in isSelected = !newValue } + } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 1052067133..468cd65441 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -32,8 +32,7 @@ struct NCMediaNew: View { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in - NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) - { tappedThumbnail, isSelected in + NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) { tappedThumbnail, isSelected in //TODO: Put in VM if vm.isInSelectMode, isSelected { @@ -49,12 +48,22 @@ struct NCMediaNew: View { } } onCellContextMenuItemSelected: { thumbnail, selection in let selectedMetadata = thumbnail.metadata - + switch selection { + case .addToFavorites: + vm.addToFavorites(metadata: selectedMetadata) case .detail: NCActionCenter.shared.openShare(viewController: parent, metadata: selectedMetadata, page: .activity) case .openIn: vm.openIn(metadata: selectedMetadata) + case .saveToPhotos: + vm.saveToPhotos(metadata: selectedMetadata) + case .viewInFolder: + vm.viewInFolder(metadata: selectedMetadata) + case .modify: + vm.modify(metadata: selectedMetadata) + case .delete: + vm.delete(metadatas: selectedMetadata) } } diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 98f040dd7f..9372747ff1 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -27,7 +27,7 @@ struct NCMediaRow: View { } } else { ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in - NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onSelected: onCellSelected, onContextMenuItemSelected: onCellContextMenuItemSelected) + NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onSelected: onCellSelected, onContextMenuItemSelected: onCellContextMenuItemSelected, isFavorite: thumbnail.metadata.favorite) } } } diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 50a650acb0..2bbfe5df4d 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -119,18 +119,14 @@ import Combine let notLocked = selectedMetadatas.allSatisfy { !$0.lock } if notLocked { - Task { - var error = NKError() - var ocId: [String] = [] - for metadata in selectedMetadatas where error == .success { - error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false) - if error == .success { - ocId.append(metadata.ocId) - } - } - NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "onlyLocalCache": false, "error": error]) + delete(metadatas: selectedMetadatas) + } + } - isInSelectMode = false + func addToFavorites(metadata: tableMetadata) { + NCNetworking.shared.favoriteMetadata(metadata) { error in + if error != .success { + NCContentPresenter.shared.showError(error: error) } } } @@ -157,6 +153,73 @@ import Combine } } } + + func saveToPhotos(metadata: tableMetadata) { + if let livePhoto = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) { + NCActionCenter.shared.saveLivePhoto(metadata: metadata, metadataMOV: livePhoto) + } else if CCUtility.fileProviderStorageExists(metadata) { + NCActionCenter.shared.saveAlbum(metadata: metadata) + } else { + NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorSaveAlbum, notificationCenterProgressTask: false) { request in +// downloadRequest = request + } progressHandler: { progress in +// hud.progress = Float(progress.fractionCompleted) + } completion: { afError, error in +// if error == .success || afError?.isExplicitlyCancelledError ?? false { +// hud.dismiss() +// } else { +// hud.indicatorView = JGProgressHUDErrorIndicatorView() +// hud.textLabel.text = error.description +// hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond) +// } + } + } + } + + func viewInFolder(metadata: tableMetadata) { + NCActionCenter.shared.openFileViewInFolder(serverUrl: metadata.serverUrl, fileNameBlink: metadata.fileName, fileNameOpen: nil) + } + + func modify(metadata: tableMetadata) { + if CCUtility.fileProviderStorageExists(metadata) { + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile, userInfo: ["ocId": metadata.ocId, "selector": NCGlobal.shared.selectorLoadFileQuickLook, "error": NKError(), "account": metadata.account]) + } else { +// hud.show(in: viewController.view) + NCNetworking.shared.download(metadata: metadata, selector: NCGlobal.shared.selectorLoadFileQuickLook, notificationCenterProgressTask: false) { request in +// downloadRequest = request + } progressHandler: { progress in +// hud.progress = Float(progress.fractionCompleted) + } completion: { afError, error in +// if error == .success || afError?.isExplicitlyCancelledError ?? false { +// hud.dismiss() +// } else { +// hud.indicatorView = JGProgressHUDErrorIndicatorView() +// hud.textLabel.text = error.description +// hud.dismiss(afterDelay: NCGlobal.shared.dismissAfterSecond) +// } + } + } + } + + func delete(metadatas: tableMetadata...) { + delete(metadatas: metadatas) + } + + func delete(metadatas: [tableMetadata]) { + Task { + var error = NKError() + var ocId: [String] = [] + for metadata in metadatas where error == .success { + error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false) + if error == .success { + ocId.append(metadata.ocId) + } + } + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocId, "onlyLocalCache": false, "error": error]) + + isInSelectMode = false + } + } } // MARK: Notifications diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 9eebb73516..7e0adb5290 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -546,7 +546,7 @@ "_tite_footer_download_wwan_" = " Wi-Fi network required, %lu %@ to download"; "_tite_footer_download_" = "%lu %@ to download"; "_limited_dimension_" = "Maximum size reached"; -"_save_selected_files_" = "Save to photo gallery"; +"_save_selected_files_" = "Save to Photos"; "_file_not_saved_cameraroll_" = "Error: File not saved in photo album"; "_file_saved_cameraroll_" = "File saved in photo album"; "_directory_on_top_yes_" = "✓ Folders on top"; From 200a6cce2e73957afe52b4ecd3593a96be29c92d Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Sep 2023 12:27:36 +0200 Subject: [PATCH 097/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 103 +++++++++--------- iOSClient/Media/NCMediaNew.swift | 36 +++++- iOSClient/Media/NCMediaViewModel.swift | 25 +++++ .../en.lproj/Localizable.strings | 2 +- 4 files changed, 108 insertions(+), 58 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 9e719d5c48..7c0d6d2fc9 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -12,7 +12,7 @@ import Shimmer import NextcloudKit enum ContextMenuSelection { - case addToFavorites, detail, openIn, saveToPhotos, viewInFolder, modify, delete + case addToFavorites, details, openIn, saveToPhotos, viewInFolder, modify, delete } struct NCMediaCell: View { @@ -31,57 +31,6 @@ struct NCMediaCell: View { let image = Image(uiImage: thumbnail.image) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") - .contextMenu(menuItems: { - Button { - isFavorite.toggle() - onContextMenuItemSelected(thumbnail, .addToFavorites) - } label: { - Label(isFavorite ? - NSLocalizedString("_remove_favorites_", comment: "") : - NSLocalizedString("_add_favorites_", comment: ""), systemImage: "star.fill") - } - - Button { - onContextMenuItemSelected(thumbnail, .detail) - } label: { - Label(NSLocalizedString("_details_", comment: ""), systemImage: "info") - } - - Button { - onContextMenuItemSelected(thumbnail, .openIn) - } label: { - Label(NSLocalizedString("_open_in_", comment: ""), systemImage: "info") - } - - Button { - onContextMenuItemSelected(thumbnail, .saveToPhotos) - } label: { - Label(NSLocalizedString("_save_selected_files_", comment: ""), systemImage: "square.and.arrow.down") - } - - Button { - onContextMenuItemSelected(thumbnail, .viewInFolder) - } label: { - Label(NSLocalizedString("_view_in_folder_", comment: ""), systemImage: "folder.fill") - } - - Button { - onContextMenuItemSelected(thumbnail, .modify) - } label: { - Label(NSLocalizedString("_modify_", comment: ""), systemImage: "pencil.tip.crop.circle") - } - - Button(role: .destructive) { - showDeleteConfirmation = true - } label: { - Label(NSLocalizedString("_delete_file_", comment: ""), systemImage: "pencil.tip.crop.circle") - } - }) - .confirmationDialog("", isPresented: $showDeleteConfirmation) { - Button(NSLocalizedString("_delete_file_", comment: ""), role: .destructive) { - onContextMenuItemSelected(thumbnail, .delete) - } - } ZStack(alignment: .center) { if thumbnail.isPlaceholderImage { @@ -130,7 +79,57 @@ struct NCMediaCell: View { .onChange(of: isInSelectMode) { newValue in isSelected = !newValue } + .contextMenu(menuItems: { + Button { + onContextMenuItemSelected(thumbnail, .details) + } label: { + Label(NSLocalizedString("_details_", comment: ""), systemImage: "info") + } + + Button { + isFavorite.toggle() + onContextMenuItemSelected(thumbnail, .addToFavorites) + } label: { + Label(isFavorite ? + NSLocalizedString("_remove_favorites_", comment: "") : + NSLocalizedString("_add_favorites_", comment: ""), systemImage: "star.fill") + } + + Button { + onContextMenuItemSelected(thumbnail, .openIn) + } label: { + Label(NSLocalizedString("_open_in_", comment: ""), systemImage: "square.and.arrow.up") + } + + Button { + onContextMenuItemSelected(thumbnail, .saveToPhotos) + } label: { + Label(NSLocalizedString("_save_selected_files_", comment: ""), systemImage: "square.and.arrow.down") + } + Button { + onContextMenuItemSelected(thumbnail, .viewInFolder) + } label: { + Label(NSLocalizedString("_view_in_folder_", comment: ""), systemImage: "folder.fill") + } + + Button { + onContextMenuItemSelected(thumbnail, .modify) + } label: { + Label(NSLocalizedString("_modify_", comment: ""), systemImage: "pencil.tip.crop.circle") + } + + Button(role: .destructive) { + showDeleteConfirmation = true + } label: { + Label(NSLocalizedString("_delete_file_", comment: ""), systemImage: "trash") + } + }) + .confirmationDialog("", isPresented: $showDeleteConfirmation) { + Button(NSLocalizedString("_delete_file_", comment: ""), role: .destructive) { + onContextMenuItemSelected(thumbnail, .delete) + } + } } } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 468cd65441..c282773469 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -52,7 +52,7 @@ struct NCMediaNew: View { switch selection { case .addToFavorites: vm.addToFavorites(metadata: selectedMetadata) - case .detail: + case .details: NCActionCenter.shared.openShare(viewController: parent, metadata: selectedMetadata, page: .activity) case .openIn: vm.openIn(metadata: selectedMetadata) @@ -125,7 +125,7 @@ struct NCMediaNew: View { .cornerRadius(.infinity) if vm.isInSelectMode, !vm.selectedMetadatas.isEmpty { - ToolbarCircularButton(imageSystemName: "trash.fill", toolbarItemsColor: $toolbarItemsColor) + ToolbarCircularButton(imageSystemName: "trash.fill", color: .red) .onTapGesture { showDeleteConfirmation = true } @@ -137,6 +137,22 @@ struct NCMediaNew: View { } Menu { + if vm.isInSelectMode, !vm.selectedMetadatas.isEmpty { + Section { + Button { + vm.copyOrMoveSelectedMetadataInApp() + } label: { + Label(NSLocalizedString("_move_selected_files_", comment: ""), systemImage: "arrow.up.right.square") + } + + Button { + vm.copySelectedMetadata() + } label: { + Label(NSLocalizedString("_copy_file_", comment: ""), systemImage: "doc.on.doc") + } + } + } + Section { Picker(NSLocalizedString("_media_view_options_", comment: ""), selection: $vm.filter) { Label(NSLocalizedString("_media_viewimage_show_", comment: ""), systemImage: "photo.fill").tag(Filter.onlyPhotos) @@ -162,7 +178,7 @@ struct NCMediaNew: View { }) } } label: { - ToolbarCircularButton(imageSystemName: "ellipsis", toolbarItemsColor: $toolbarItemsColor) + ToolbarCircularButton(imageSystemName: "ellipsis", color: $toolbarItemsColor) } } }) @@ -208,7 +224,17 @@ struct NCMediaNew: View { struct ToolbarCircularButton: View { let imageSystemName: String - @Binding var toolbarItemsColor: Color + @Binding var color: Color + + init(imageSystemName: String, color: Binding) { + self.imageSystemName = imageSystemName + self._color = color + } + + init(imageSystemName: String, color: Color) { + self.imageSystemName = imageSystemName + self._color = .constant(color) + } var body: some View { Image(systemName: imageSystemName) @@ -218,7 +244,7 @@ struct ToolbarCircularButton: View { .padding(5) .background(.ultraThinMaterial) .clipShape(Circle()) - .foregroundColor(toolbarItemsColor) + .foregroundColor(color) } } diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 2bbfe5df4d..bc419dfb84 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -50,6 +50,8 @@ import Combine case .onlyVideos: self.loadMediaFromDB(showPhotos: false, showVideos: true) } + + cancelSelection() } .store(in: &cancellables) } @@ -123,6 +125,16 @@ import Combine } } + func copyOrMoveSelectedMetadataInApp() { + NCActionCenter.shared.openSelectView(items: selectedMetadatas, indexPath: []) + cancelSelection() + } + + func copySelectedMetadata() { + copy(metadatas: selectedMetadatas) + cancelSelection() + } + func addToFavorites(metadata: tableMetadata) { NCNetworking.shared.favoriteMetadata(metadata) { error in if error != .success { @@ -220,6 +232,19 @@ import Combine isInSelectMode = false } } + + func copy(metadatas: tableMetadata...) { + copy(metadatas: metadatas) + } + + func copy(metadatas: [tableMetadata]) { + NCActionCenter.shared.copyPasteboard(pasteboardOcIds: metadatas.compactMap({ $0.ocId })) + } + + private func cancelSelection() { + self.isInSelectMode = false + self.selectedMetadatas.removeAll() + } } // MARK: Notifications diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 7e0adb5290..74cce65f38 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -496,7 +496,7 @@ "_sorted_by_size_largest_" = "Sorted by largest"; "_delete_selected_files_" = "Delete files"; "_move_selected_files_" = "Move files"; -"_move_or_copy_selected_files_" = "Move or copy files"; +"_move_or_copy_selected_files_" = "Move or copy in app"; "_download_selected_files_" = "Download files"; "_download_selected_files_folders_" = "Download files and folders"; "_error_operation_canc_" = "Error: Operation canceled."; From 4b653273f4f3053eb7a2cf15f485de62fcad82a6 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Sep 2023 12:27:46 +0200 Subject: [PATCH 098/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index bc419dfb84..5827733c53 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -51,7 +51,7 @@ import Combine self.loadMediaFromDB(showPhotos: false, showVideos: true) } - cancelSelection() + self.cancelSelection() } .store(in: &cancellables) } From 95ac5884a33d6491d82d83ceb5501cf4545901f4 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Sep 2023 12:33:18 +0200 Subject: [PATCH 099/103] Add snapshot test Signed-off-by: Milen Pivchev --- Tests/NextcloudSnapshotTests/NextcloudSnapshotTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/NextcloudSnapshotTests/NextcloudSnapshotTests.swift b/Tests/NextcloudSnapshotTests/NextcloudSnapshotTests.swift index 211565e1ea..2e23a2db10 100644 --- a/Tests/NextcloudSnapshotTests/NextcloudSnapshotTests.swift +++ b/Tests/NextcloudSnapshotTests/NextcloudSnapshotTests.swift @@ -21,4 +21,8 @@ final class NextcloudSnapshotTests: XCTestCase { func test_CapalitiesView() { NCCapabilitiesView_Previews.snapshots.assertSnapshots(as: .imageHEIC) } + + func test_MediaView() { + NCMediaNew_Previews.snapshots.assertSnapshots(as: .imageHEIC) + } } From 13b45df3a0d9ac85bc035973bab1e0ca2f3b3503 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Sep 2023 18:55:59 +0200 Subject: [PATCH 100/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 34 +++++++++++++++++-- iOSClient/Media/NCMediaViewModel.swift | 13 ++++++- .../en.lproj/Localizable.strings | 10 +++--- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index c282773469..f2008f0a67 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -25,6 +25,8 @@ struct NCMediaNew: View { @State var toolbarColors = [Color.clear] @State private var showDeleteConfirmation = false + @State private var showPlayFromURLAlert = false + @State private var playFromUrlString = "" var body: some View { GeometryReader { outerProxy in @@ -130,7 +132,7 @@ struct NCMediaNew: View { showDeleteConfirmation = true } .confirmationDialog("", isPresented: $showDeleteConfirmation) { - Button("Delete selected media", role: .destructive) { + Button(NSLocalizedString("_delete_selected_media_", comment: ""), role: .destructive) { vm.deleteSelectedMetadata() } } @@ -170,10 +172,19 @@ struct NCMediaNew: View { } Section { - Button(action: {}, label: { + Button(action: { + var documentPickerViewController: NCDocumentPickerViewController? + + if let tabBarController = vm.appDelegate?.window?.rootViewController as? UITabBarController { + documentPickerViewController = NCDocumentPickerViewController(tabBarController: tabBarController, isViewerMedia: true, allowsMultipleSelection: false, viewController: parent) + } + }, label: { Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") }) - Button(action: {}, label: { + + Button(action: { + showPlayFromURLAlert = true + }, label: { Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") }) } @@ -199,6 +210,18 @@ struct NCMediaNew: View { .onChange(of: vm.isInSelectMode) { newValue in if newValue == false { vm.selectedMetadatas.removeAll() } } + .alert("", isPresented: $showPlayFromURLAlert) { + TextField("https://...", text: $playFromUrlString) + .keyboardType(.URL) + .textContentType(.URL) + + Button(NSLocalizedString("_cancel_", comment: ""), role: .cancel) {} + Button(NSLocalizedString("_ok_", comment: "")) { + playVideoFromUrl() + } + } message: { + Text(NSLocalizedString("_valid_video_url_", comment: "")) + } } private func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { @@ -220,6 +243,11 @@ struct NCMediaNew: View { parent.present(navigationController, animated: true, completion: nil) } + + private func playVideoFromUrl() { + guard let metadata = vm.getMetadataFromUrl(playFromUrlString) else { return } + NCViewer.shared.view(viewController: parent, metadata: metadata, metadatas: [metadata], imageIcon: nil) + } } struct ToolbarCircularButton: View { diff --git a/iOSClient/Media/NCMediaViewModel.swift b/iOSClient/Media/NCMediaViewModel.swift index 5827733c53..149fd3f8d8 100644 --- a/iOSClient/Media/NCMediaViewModel.swift +++ b/iOSClient/Media/NCMediaViewModel.swift @@ -20,7 +20,7 @@ import Combine private var livePhoto: Bool = false private var predicateDefault: NSPredicate? private var predicate: NSPredicate? - private let appDelegate = UIApplication.shared.delegate as? AppDelegate + internal let appDelegate = UIApplication.shared.delegate as? AppDelegate private var cancellables: Set = [] @@ -213,6 +213,17 @@ import Combine } } + func getMetadataFromUrl(_ urlString: String) -> tableMetadata? { + guard let url = URL(string: urlString), let appDelegate else { return nil } + + let fileName = url.lastPathComponent + let metadata = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: NSUUID().uuidString, serverUrl: "", urlBase: appDelegate.urlBase, url: urlString, contentType: "") + + NCManageDatabase.shared.addMetadata(metadata) + + return metadata + } + func delete(metadatas: tableMetadata...) { delete(metadatas: metadatas) } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 74cce65f38..7d6b9640ed 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -584,14 +584,12 @@ "_status_in_upload_" = "In upload"; "_status_uploading_" = "Uploading"; "_status_upload_error_" = "Error, waiting for upload"; -"_select_media_folder_" = "Select the \"Media\" folder"; +"_select_media_folder_" = "Set Media folder"; "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only videos"; "_media_show_all_" = "Show both"; "_media_view_options_" = "View options"; -"_media_by_created_date_" = "Sort by created date"; -"_media_by_upload_date_" = "Sort by upload date"; -"_media_by_modified_date_" = "Sort by modified date"; +"_delete_selected_media_" = "Delete selected media"; "_insert_password_pfd_" = "Secured PDF. Enter password"; "_password_pdf_error_" = "Wrong password"; "_error_download_photobrowser_" = "Error: Unable to download photo"; @@ -929,8 +927,8 @@ "_creation_" = "Creation"; "_modified_" = "Modified"; "_group_folders_" = "Group folders"; -"_play_from_files_" = "Play movie by Files"; -"_play_from_url_" = "Play movie by URL"; +"_play_from_files_" = "Play video from files"; +"_play_from_url_" = "Play video from URL"; "_valid_video_url_" = "Insert a valid URL"; "_deletion_progess_" = "Deletion in progress"; "_copying_progess_" = "Copying in progress"; From 244c5d5ed65115fca3aefcd78c16731df9c2654e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Sep 2023 19:02:20 +0200 Subject: [PATCH 101/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 56 +++++++++++++++----------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index f2008f0a67..f03cb746c4 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -153,40 +153,38 @@ struct NCMediaNew: View { Label(NSLocalizedString("_copy_file_", comment: ""), systemImage: "doc.on.doc") } } - } - - Section { - Picker(NSLocalizedString("_media_view_options_", comment: ""), selection: $vm.filter) { - Label(NSLocalizedString("_media_viewimage_show_", comment: ""), systemImage: "photo.fill").tag(Filter.onlyPhotos) + } else { + Section { + Picker(NSLocalizedString("_media_view_options_", comment: ""), selection: $vm.filter) { + Label(NSLocalizedString("_media_viewimage_show_", comment: ""), systemImage: "photo.fill").tag(Filter.onlyPhotos) - Label(NSLocalizedString("_media_viewvideo_show_", comment: ""), systemImage: "video.fill").tag(Filter.onlyVideos) + Label(NSLocalizedString("_media_viewvideo_show_", comment: ""), systemImage: "video.fill").tag(Filter.onlyVideos) - Text(NSLocalizedString("_media_show_all_", comment: "")).tag(Filter.all) - }.pickerStyle(.menu) + Text(NSLocalizedString("_media_show_all_", comment: "")).tag(Filter.all) + }.pickerStyle(.menu) - Button { - selectMediaFolder() - } label: { - Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + Button { + selectMediaFolder() + } label: { + Label(NSLocalizedString("_select_media_folder_", comment: ""), systemImage: "folder") + } } - } - - Section { - Button(action: { - var documentPickerViewController: NCDocumentPickerViewController? - if let tabBarController = vm.appDelegate?.window?.rootViewController as? UITabBarController { - documentPickerViewController = NCDocumentPickerViewController(tabBarController: tabBarController, isViewerMedia: true, allowsMultipleSelection: false, viewController: parent) - } - }, label: { - Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") - }) - - Button(action: { - showPlayFromURLAlert = true - }, label: { - Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") - }) + Section { + Button(action: { + if let tabBarController = vm.appDelegate?.window?.rootViewController as? UITabBarController { + NCDocumentPickerViewController(tabBarController: tabBarController, isViewerMedia: true, allowsMultipleSelection: false, viewController: parent) + } + }, label: { + Label(NSLocalizedString("_play_from_files_", comment: ""), systemImage: "play.circle") + }) + + Button(action: { + showPlayFromURLAlert = true + }, label: { + Label(NSLocalizedString("_play_from_url_", comment: ""), systemImage: "link") + }) + } } } label: { ToolbarCircularButton(imageSystemName: "ellipsis", color: $toolbarItemsColor) From c9ca700199cd5435d2065af3a7f35336cd687c83 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 2 Oct 2023 12:54:27 +0200 Subject: [PATCH 102/103] WIP Signed-off-by: Milen Pivchev --- iOSClient/Media/Cell/NCMediaCell.swift | 4 +-- iOSClient/Media/NCMediaNew.swift | 35 ++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index 7c0d6d2fc9..df8cf2837a 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -30,7 +30,6 @@ struct NCMediaCell: View { var body: some View { let image = Image(uiImage: thumbnail.image) .resizable() - .trackVisibility(id: CCUtility.getTitleSectionDate(thumbnail.metadata.date as Date) ?? "") ZStack(alignment: .center) { if thumbnail.isPlaceholderImage { @@ -150,7 +149,8 @@ struct NCMediaLoadingCell: View { Image(uiImage: UIImage()) .resizable() .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "")// TODO: Fix spacing - .frame(width: (geometryProxy.size.width - spacing) / CGFloat(itemsInRow), height: 130) + .aspectRatio(1.5, contentMode: .fit) + .frame(width: (geometryProxy.size.width - spacing) / CGFloat(itemsInRow)) .redacted(reason: .placeholder) .shimmering(gradient: gradient, bandSize: 0.7) } diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index f03cb746c4..6805f920a4 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -15,7 +15,6 @@ import VisibilityTrackingScrollView struct NCMediaNew: View { @StateObject private var vm = NCMediaViewModel() @EnvironmentObject var parent: NCMediaUIKitWrapper - @State private var columns = 2 @State private var title = "Media" @State private var isScrolledToTop = true @State private var tappedMetadata = tableMetadata() @@ -28,12 +27,16 @@ struct NCMediaNew: View { @State private var showPlayFromURLAlert = false @State private var playFromUrlString = "" + @State private var columnCountStages = [2, 3, 4] + @State private var columnCountStagesIndex = 0 + @State private var columnCountChanged = false + var body: some View { GeometryReader { outerProxy in ZStack(alignment: .top) { VisibilityTrackingScrollView(action: cellVisibilityDidChange) { LazyVStack(alignment: .leading, spacing: 2) { - ForEach(vm.metadatas.chunked(into: columns), id: \.self) { rowMetadatas in + ForEach(vm.metadatas.chunked(into: columnCountStages[columnCountStagesIndex]), id: \.self) { rowMetadatas in NCMediaRow(metadatas: rowMetadatas, geometryProxy: outerProxy, isInSelectMode: $vm.isInSelectMode) { tappedThumbnail, isSelected in //TODO: Put in VM @@ -199,15 +202,18 @@ struct NCMediaNew: View { } .onRotate { orientation in if orientation.isLandscapeHardCheck { - columns = 6 + columnCountStages = [4, 6, 8] } else { - columns = 2 + columnCountStages = [2, 3, 4] } } .onAppear { vm.loadMediaFromDB() } .onChange(of: vm.isInSelectMode) { newValue in if newValue == false { vm.selectedMetadatas.removeAll() } } + .onChange(of: columnCountStagesIndex) { _ in + columnCountChanged = true + } .alert("", isPresented: $showPlayFromURLAlert) { TextField("https://...", text: $playFromUrlString) .keyboardType(.URL) @@ -220,8 +226,22 @@ struct NCMediaNew: View { } message: { Text(NSLocalizedString("_valid_video_url_", comment: "")) } + .gesture( + MagnificationGesture(minimumScaleDelta: 0) + .onChanged { scale in + print(scale) + if !columnCountChanged { + let newZoom = Double(columnCountStages[columnCountStagesIndex]) * 1 / scale + let newZoomIndex = findClosestZoomIndex(value: newZoom) + columnCountStagesIndex = newZoomIndex + } + } + .onEnded({ _ in + columnCountChanged = false + }) + ) } - +x private func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { DispatchQueue.main.async { if let date = tracker.topVisibleView, !date.isEmpty { @@ -230,6 +250,11 @@ struct NCMediaNew: View { } } + func findClosestZoomIndex(value: Double) -> Int { + let distanceArray = columnCountStages.map { abs(Double($0) - value) } // absolute difference between zoom stages and actual pinch zoom + return distanceArray.indices.min(by: {distanceArray[$0] < distanceArray[$1]}) ?? 0 // return index of element that is "closest" + } + private func selectMediaFolder() { guard let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController, let viewController = navigationController.topViewController as? NCSelect From 8be9b8c067c03965e74eed2f45e6013f5137bf3f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 2 Oct 2023 12:55:24 +0200 Subject: [PATCH 103/103] Fix Signed-off-by: Milen Pivchev --- iOSClient/Media/NCMediaNew.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Media/NCMediaNew.swift b/iOSClient/Media/NCMediaNew.swift index 6805f920a4..888c0c2737 100644 --- a/iOSClient/Media/NCMediaNew.swift +++ b/iOSClient/Media/NCMediaNew.swift @@ -241,7 +241,7 @@ struct NCMediaNew: View { }) ) } -x + private func cellVisibilityDidChange(_ id: String, change: VisibilityChange, tracker: VisibilityTracker) { DispatchQueue.main.async { if let date = tracker.topVisibleView, !date.isEmpty {