From 8b22e7145b1c8ee4dbbecc3ef25b58df6ccb4440 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 20 Oct 2023 13:09:05 +0200 Subject: [PATCH] Squashed commit of the following: commit 28c5001295091e46b6f40644a8ea404887994c66 Author: Milen Pivchev Date: Fri Oct 20 13:08:44 2023 +0200 WIP Signed-off-by: Milen Pivchev commit a67c7a0a22fbfafbfd9d8fa8ae145ca687133f2e Merge: b42a1c8d0 510bdb453 Author: Milen Pivchev Date: Fri Oct 20 13:05:10 2023 +0200 Merge branch 'develop' of https://github.com/nextcloud/ios into media-view-performance-optimizations Signed-off-by: Milen Pivchev # Conflicts: # iOSClient/Media/NCMediaCache.swift commit 510bdb453b4c40b9666b78e2643808beaa995a0c Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Fri Oct 20 12:17:49 2023 +0200 lint Signed-off-by: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> commit 6a560e3d8080181ab32ea00c29c085b2402c994e Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Fri Oct 20 12:17:11 2023 +0200 improvements Signed-off-by: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> commit a232a022bbd75207a7346fa901560e44abd164d9 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Fri Oct 20 12:04:24 2023 +0200 lint Signed-off-by: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> commit 74d530298a5a9e7fb41d726f0bc4b31decc03897 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Fri Oct 20 12:01:26 2023 +0200 lint Signed-off-by: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> commit 3de2089f3ed24a48296c6b31fa1535b070d21386 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Fri Oct 20 11:41:07 2023 +0200 lint Signed-off-by: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> commit 01b701418354d945c3fd5c1d8e2371fcadd8628f Author: Nextcloud bot Date: Fri Oct 20 02:42:09 2023 +0000 Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot commit b42a1c8d081ab03dcb99d3f8e13b824fccd136a7 Author: Milen Pivchev Date: Thu Oct 19 18:49:44 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 073c390d002f4d01d019a503e9b4f430e90dac1a Merge: 2475daa25 928a08897 Author: Milen Pivchev Date: Thu Oct 19 18:18:43 2023 +0200 Merge branch '2399-aspect-ratio-in-media-view' into media-view-performance-optimizations Signed-off-by: Milen Pivchev # Conflicts: # Nextcloud.xcodeproj/project.pbxproj commit 472b560882eb6c39d78a6b24cbccd5288df997be Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Thu Oct 19 17:43:49 2023 +0200 update NextcloudNit Signed-off-by: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> commit 2475daa25923764c724f108d063c8db1aef13c3d Merge: 6012f3b90 4ca1fbe75 Author: Milen Pivchev Date: Thu Oct 19 14:33:34 2023 +0200 Merge branch '2399-aspect-ratio-in-media-view' of https://github.com/nextcloud/ios into media-view-performance-optimizations commit 6012f3b9086f204a1a98f08e0c30127e2d3c14be Merge: f55c8fbf4 9747ad824 Author: Milen Pivchev Date: Thu Oct 19 12:28:17 2023 +0200 Merge branch '2399-aspect-ratio-in-media-view' of https://github.com/nextcloud/ios into media-view-performance-optimizations commit f55c8fbf4b3cb95b472ac500ae28eaf0717668a4 Author: Milen Pivchev Date: Thu Oct 19 12:24:03 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 236c98b1a5ed166d5248d5637d0dd3b1971aad51 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 16:42:52 2023 +0200 verify etag commit 7b2eab2a707d36a262b3200fd50b2908f83518ab Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 12:28:22 2023 +0200 Update NCMediaManager.swift commit 1cbe26b803e682f72ccc50ac50d4f1b58427a918 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 12:23:01 2023 +0200 optimized commit 626726672ac97b7001db5a703f42e652bad2d5da Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:24:59 2023 +0200 Update NCMediaManager.swift commit 7ecc1b4d73ae8a6c5d42eee1490ab7bca35ca478 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:22:34 2023 +0200 Update AppDelegate.swift commit 062c224c61668d014b0341d11fc06fcdbdd107b3 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:15:32 2023 +0200 Update NCMediaManager.swift commit 7357d6a6f1aeaab66a434858b5db27dd15c31f7a Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:12:24 2023 +0200 Update NCMediaManager.swift commit 2a866d6430e48f5b34728fa391d622e0de4da9d3 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:10:43 2023 +0200 Update NCMediaManager.swift commit 3212e92ecdc4875deeed6ae86f61892287fae07e Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:09:07 2023 +0200 Update NCMediaManager.swift commit d493847d29cb02fd4709d382444162c9cf095f63 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:03:36 2023 +0200 Update NCMediaManager.swift commit e9a5d97e78172b3301ad9fb9b4a3f62cbb0c1185 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 11:02:11 2023 +0200 Update NCMediaManager.swift commit e73c0af8e17be76741217db720f8927baf5c9781 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 10:58:25 2023 +0200 Update AppDelegate.swift commit eee1b89c088f42dd8347ad246532ee2a0e9e3700 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 10:55:28 2023 +0200 Update NCMediaManager.swift commit 753841fdc78618359a662519767f83837f665776 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 10:52:25 2023 +0200 Update NCMediaManager.swift commit 7b94537e55ff660699a5202269fbba60a1963983 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Tue Oct 17 10:50:13 2023 +0200 coding commit b7bb1fef870050aad8bc379a63fa4fb1ff0fd9a6 Author: Marino Faggiana <8616947+marinofaggiana@users.noreply.github.com> Date: Mon Oct 16 18:13:11 2023 +0200 test test commit 873df2df54966af42a6c6439c202aa1b5ebce7a3 Author: Milen Pivchev Date: Fri Oct 13 19:30:38 2023 +0200 More optimizations Signed-off-by: Milen Pivchev commit fcf9f5af04ed7112c4bcfd3aa2829d6b4a421c8c Author: Milen Pivchev Date: Fri Oct 13 18:56:48 2023 +0200 Replace LazyVStack with List Signed-off-by: Milen Pivchev commit 449641c79ad38dca0e5981c2bd0491972fa298e2 Author: Milen Pivchev Date: Fri Oct 13 18:21:29 2023 +0200 Fix some rows not updating on rotate/zoom Signed-off-by: Milen Pivchev commit 7091d2ea1c3a1f60764f5edec5e9a61813b0e160 Author: Milen Pivchev Date: Fri Oct 13 18:14:39 2023 +0200 WIP Signed-off-by: Milen Pivchev commit bc571bcb5149936c64fa521474d5b42330416fc4 Author: Milen Pivchev Date: Fri Oct 13 12:12:04 2023 +0200 Refactor Signed-off-by: Milen Pivchev commit 7172b43e7b4b512ece66b9363711341aa099a65d Author: Milen Pivchev Date: Fri Oct 13 10:48:32 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 3f97177cb466aa2ffe321458e4c99eee937a821e Author: Milen Pivchev Date: Fri Oct 13 10:46:13 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 6cd03fc82ccf9fbfb47598a96e8a108e9823c7a4 Author: Milen Pivchev Date: Thu Oct 12 18:57:26 2023 +0200 Fix scroll offset bug Signed-off-by: Milen Pivchev commit adc56705c50132ba322d31e78dbc2c270e433bd0 Author: Milen Pivchev Date: Thu Oct 12 17:43:13 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 15ed95540dd405842f2c6696f79a14c14d60ad66 Author: Milen Pivchev Date: Wed Oct 11 18:04:33 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 11062244235c301c4c714120a478b4e5218f700f Author: Milen Pivchev Date: Wed Oct 11 14:43:38 2023 +0200 Fix compile Signed-off-by: Milen Pivchev commit f668bdb8d132a2ee3510f842e53d557edd527b29 Merge: 5ac583b90 ae6b88442 Author: Milen Pivchev Date: Wed Oct 11 12:36:11 2023 +0200 Merge branch '2399-aspect-ratio-in-media-view' of https://github.com/nextcloud/ios into media-view-performance-optimizations commit 5ac583b90f532000b189d652f15b7a8c30313487 Author: Milen Pivchev Date: Wed Oct 11 12:10:37 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 01051100783a0d71b40312059842d0a7bd9f2c2e Author: Milen Pivchev Date: Wed Oct 11 12:08:14 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 40011dfc8f29c2d7579e527a50a14c2bac9e0c0e Author: Milen Pivchev Date: Wed Oct 11 12:06:54 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 7f948e13fd6621deed19ea3e917766b4e1723b35 Merge: 2d8cfb7d9 b8b08f4ea Author: Milen Pivchev Date: Wed Oct 11 11:34:47 2023 +0200 Merge branch 'media-view-performance-optimizations' of https://github.com/nextcloud/ios into media-view-performance-optimizations commit 2d8cfb7d9a070e771986f0044fe96806d10c904a Author: Milen Pivchev Date: Wed Oct 11 11:34:33 2023 +0200 WIP Signed-off-by: Milen Pivchev commit b8b08f4ea8b6f4199ec6cdda1f67e15b53cd00a6 Merge: 60de2d96d 01f2c315f Author: Marino Faggiana Date: Wed Oct 11 10:36:43 2023 +0200 Merge branch 'media-view-performance-optimizations' of https://github.com/nextcloud/ios into media-view-performance-optimizations commit 01f2c315f0d7ef3ae56035df3d0540f6fdb32f69 Author: Milen Pivchev Date: Tue Oct 10 18:55:22 2023 +0200 WIP Signed-off-by: Milen Pivchev commit b0f4b4622d585c37aecd07b5218eaa6dc1e6cf24 Author: Milen Pivchev Date: Tue Oct 10 18:52:34 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 134c91e1bb9bf5c92bf0dc2e452508849025d4e9 Author: Milen Pivchev Date: Tue Oct 10 18:05:08 2023 +0200 WIP Signed-off-by: Milen Pivchev commit fce442356af52cbabfda9e63e6e832099a5bbd69 Author: Milen Pivchev Date: Tue Oct 10 17:31:32 2023 +0200 WIP Signed-off-by: Milen Pivchev commit bdb5f4f590f80f06f09adb73469093c63e001911 Author: Milen Pivchev Date: Tue Oct 10 16:56:04 2023 +0200 WIP Signed-off-by: Milen Pivchev commit ffa930a62fb93c04150c2f45ff835b91417a44ed Author: Milen Pivchev Date: Tue Oct 10 14:15:50 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 60de2d96dd07391018f1fdd345cf0b2d8306298d Author: Marino Faggiana Date: Tue Oct 10 11:13:54 2023 +0200 test Signed-off-by: Marino Faggiana commit 179993a0ccb9b0d61bd3dda65eea1196749e22e1 Author: Milen Pivchev Date: Mon Oct 9 19:37:35 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 662040650cb00c016b4c219b73827e10ab5853ba Author: Milen Pivchev Date: Mon Oct 9 17:08:56 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 221a24806e6b6e41d07f1eb161dde08d3dc58e70 Author: Milen Pivchev Date: Mon Oct 9 15:53:01 2023 +0200 Add thread-safe LRU Cache Signed-off-by: Milen Pivchev commit d895e511e2462816ee6bd2e70f614ba83e824bf8 Author: Milen Pivchev Date: Mon Oct 9 15:00:59 2023 +0200 WIP Signed-off-by: Milen Pivchev commit d46aee27804ad4d574d21ca555678494a2cc15e0 Author: Milen Pivchev Date: Fri Oct 6 13:21:25 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 14b29c45c28430989d249671ca2fff1c4d29c261 Author: Milen Pivchev Date: Fri Oct 6 13:17:16 2023 +0200 WIP Signed-off-by: Milen Pivchev commit fb65918d9906d45dc992f34004e4db0f179080f4 Author: Milen Pivchev Date: Fri Oct 6 11:08:31 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 7c7762012651c1fe64b74a45d536f07c9acf0f8b Author: Milen Pivchev Date: Thu Oct 5 19:29:33 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 689f4416893109c0c148e9ce5742a317a7934d8d Author: Milen Pivchev Date: Thu Oct 5 17:04:18 2023 +0200 WIP Signed-off-by: Milen Pivchev commit fb7b1c636307f41a05789d5f28e11ac5315052e3 Author: Milen Pivchev Date: Thu Oct 5 15:11:05 2023 +0200 WIP Signed-off-by: Milen Pivchev commit 9d4a71ce19e4f50f072a4e5addca74aa5ea9248e Author: Milen Pivchev Date: Thu Oct 5 13:36:20 2023 +0200 Change LRU cache Signed-off-by: Milen Pivchev commit 28d5092b5fe05aa025baffc9dc85b4b390960020 Author: Milen Pivchev Date: Wed Oct 4 17:52:35 2023 +0200 WIP Signed-off-by: Milen Pivchev Signed-off-by: Milen Pivchev --- .../Extensions/SwiftUIView+Extensions.swift | 22 ++++ iOSClient/Media/Cell/NCMediaCell.swift | 32 +++-- iOSClient/Media/NCMediaCache.swift | 109 ++++++++++++++++ iOSClient/Media/NCMediaRow.swift | 29 +++-- iOSClient/Media/NCMediaRowViewModel.swift | 109 ++++++++++------ iOSClient/Media/NCMediaScrollView.swift | 120 ++++++++++++++++++ .../Networking/E2EE/NCNetworkingE2EE.swift | 6 +- .../hu.lproj/Localizable.strings | Bin 126784 -> 126638 bytes iOSClient/Utility/PreferenceKeys.swift | 6 + 9 files changed, 372 insertions(+), 61 deletions(-) create mode 100644 iOSClient/Media/NCMediaCache.swift create mode 100644 iOSClient/Media/NCMediaScrollView.swift diff --git a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift index b6319a3e4f..32dda872fa 100644 --- a/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift +++ b/Tests/NextcloudSnapshotTests/Extensions/SwiftUIView+Extensions.swift @@ -37,6 +37,24 @@ struct DeviceOrientationViewModifier: ViewModifier { } } +struct ViewDidFirstAppearModifier: ViewModifier { + @State private var didLoad = false + private let action: (() -> Void)? + + init(perform action: (() -> Void)? = nil) { + self.action = action + } + + func body(content: Content) -> some View { + content.onAppear { + if didLoad == false { + didLoad = true + action?() + } + } + } +} + extension SwiftUI.View { func toVC() -> UIViewController { let vc = UIHostingController (rootView: self) @@ -47,4 +65,8 @@ extension SwiftUI.View { func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View { self.modifier(DeviceOrientationViewModifier(action: action)) } + + func onFirstAppear(perform action: (() -> Void)? = nil) -> some View { + modifier(ViewDidFirstAppearModifier(perform: action)) + } } diff --git a/iOSClient/Media/Cell/NCMediaCell.swift b/iOSClient/Media/Cell/NCMediaCell.swift index a54da587b3..1bce39099b 100644 --- a/iOSClient/Media/Cell/NCMediaCell.swift +++ b/iOSClient/Media/Cell/NCMediaCell.swift @@ -7,7 +7,6 @@ // import SwiftUI -import VisibilityTrackingScrollView import Shimmer import NextcloudKit @@ -30,7 +29,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 { @@ -136,7 +134,7 @@ struct NCMediaCell: View { struct NCMediaLoadingCell: View { let itemsInRow: Int let metadata: tableMetadata - let geometryProxy: GeometryProxy + let rowSize: CGFloat let spacing: CGFloat let gradient = Gradient(colors: [ @@ -146,14 +144,24 @@ struct NCMediaLoadingCell: View { ]) var body: some View { - ZStack { - Image(uiImage: UIImage()) - .resizable() - .trackVisibility(id: CCUtility.getTitleSectionDate(metadata.date as Date) ?? "")// TODO: Fix spacing - .aspectRatio(1.5, contentMode: .fit) - .frame(width: (geometryProxy.size.width - spacing) / CGFloat(itemsInRow)) - .redacted(reason: .placeholder) - .shimmering(gradient: gradient, bandSize: 0.7) - } +// let _ = Self._printChanges() + + Image(uiImage: UIImage()) + .resizable() +// .background(Color.random) + .aspectRatio(1.5, contentMode: .fit) + .frame(width: (UIScreen.main.bounds.width - spacing) / CGFloat(itemsInRow)) + .redacted(reason: .placeholder) + .shimmering(gradient: gradient, bandSize: 0.7) + } +} + +extension Color { + static var random: Color { + return Color( + red: .random(in: 0...1), + green: .random(in: 0...1), + blue: .random(in: 0...1) + ) } } diff --git a/iOSClient/Media/NCMediaCache.swift b/iOSClient/Media/NCMediaCache.swift new file mode 100644 index 0000000000..861c246d69 --- /dev/null +++ b/iOSClient/Media/NCMediaCache.swift @@ -0,0 +1,109 @@ +// +// NCMediaCache.swift +// Nextcloud +// +// Created by Marino Faggiana on 18/10/23. +// Copyright © 2021 Marino Faggiana. All rights reserved. +// +// Author Marino Faggiana +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +import UIKit +import LRUCache +import NextcloudKit + +@objc class NCMediaCache: NSObject { + + @objc public static let shared: NCMediaCache = { + let instance = NCMediaCache() + return instance + }() + + let limit: Int = 2000 + private typealias ThumbnailLRUCache = LRUCache + private lazy var cache: ThumbnailLRUCache = { + return ThumbnailLRUCache(countLimit: limit) + }() + + func createCache(account: String) { + + let resultsMedia = NCManageDatabase.shared.getMediaOcIdEtag(account: account) + guard !resultsMedia.isEmpty, + let directory = CCUtility.getDirectoryProviderStorage() else { return } + + let ext = ".preview.ico" + let manager = FileManager.default + let resourceKeys = Set([.nameKey, .pathKey, .fileSizeKey, .creationDateKey]) + struct FileInfo { + var path: URL + var ocId: String + var date: Date + } + var files: [FileInfo] = [] + let startDate = Date() + + if let enumerator = manager.enumerator(at: URL(fileURLWithPath: directory), includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles]) { + for case let fileURL as URL in enumerator where fileURL.lastPathComponent.hasSuffix(ext) { + let fileName = fileURL.lastPathComponent + let ocId = fileURL.deletingLastPathComponent().lastPathComponent + guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys), + let size = resourceValues.fileSize, + size > 0, + let date = resourceValues.creationDate, + let etag = resultsMedia[ocId], + fileName == etag + ext else { continue } + files.append(FileInfo(path: fileURL, ocId: ocId, date: date)) + } + } + + files.sort(by: { $0.date > $1.date }) + if let firstDate = files.first?.date, let lastDate = files.last?.date { + print("First date: \(firstDate)") + print("Last date: \(lastDate)") + } + + cache.removeAllValues() + var counter: Int = 0 + for file in files { + counter += 1 + if counter > limit { break } + autoreleasepool { + if let image = UIImage(contentsOfFile: file.path.path) { + cache.setValue(image, forKey: file.ocId) + } + } + } + + let endDate = Date() + let diffDate = endDate.timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate + NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------") + NextcloudKit.shared.nkCommonInstance.writeLog("Counter process: \(cache.count)") + NextcloudKit.shared.nkCommonInstance.writeLog("Time process: \(diffDate)") + NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------") + } + + func getImage(ocId: String) -> UIImage? { + return cache.value(forKey: ocId) + } + + func setImage(ocId: String, image: UIImage) { + cache.setValue(image, forKey: ocId) + } + + @objc func clearCache() { + cache.removeAllValues() + } +} diff --git a/iOSClient/Media/NCMediaRow.swift b/iOSClient/Media/NCMediaRow.swift index 9372747ff1..14454d256b 100644 --- a/iOSClient/Media/NCMediaRow.swift +++ b/iOSClient/Media/NCMediaRow.swift @@ -8,11 +8,15 @@ import SwiftUI import PreviewSnapshots -import VisibilityTrackingScrollView +import Queuer + +struct NCMediaRow: View, Equatable { + static func == (lhs: NCMediaRow, rhs: NCMediaRow) -> Bool { + return lhs.metadatas == rhs.metadatas + } -struct NCMediaRow: View { let metadatas: [tableMetadata] - let geometryProxy: GeometryProxy + @Binding var isInSelectMode: Bool let onCellSelected: (ScaledThumbnail, Bool) -> Void let onCellContextMenuItemSelected: (ScaledThumbnail, ContextMenuSelection) -> Void @@ -20,20 +24,29 @@ struct NCMediaRow: View { private let spacing: CGFloat = 2 var body: some View { + let _ = Self._printChanges() + HStack(spacing: spacing) { if vm.rowData.scaledThumbnails.isEmpty { - ForEach(metadatas, id: \.self) { metadata in - NCMediaLoadingCell(itemsInRow: metadatas.count, metadata: metadata, geometryProxy: geometryProxy, spacing: spacing) + ForEach(metadatas, id: \.ocId) { metadata in + NCMediaLoadingCell(itemsInRow: metadatas.count, metadata: metadata, rowSize: UIScreen.main.bounds.width, spacing: spacing) } } else { - ForEach(vm.rowData.scaledThumbnails, id: \.self) { thumbnail in + ForEach(vm.rowData.scaledThumbnails, id: \.metadata.ocId) { thumbnail in NCMediaCell(thumbnail: thumbnail, shrinkRatio: vm.rowData.shrinkRatio, isInSelectMode: $isInSelectMode, onSelected: onCellSelected, onContextMenuItemSelected: onCellContextMenuItemSelected, isFavorite: thumbnail.metadata.favorite) } } } - .onAppear { + .onFirstAppear { + vm.configure(metadatas: metadatas) + vm.downloadThumbnails(rowWidth: UIScreen.main.bounds.width, spacing: spacing) + } + .onDisappear { + vm.cancelDownloadingThumbnails() + } + .onRotate { _ in vm.configure(metadatas: metadatas) - vm.downloadThumbnails(rowWidth: geometryProxy.size.width, spacing: spacing) + vm.downloadThumbnails(rowWidth: UIScreen.main.bounds.width, spacing: spacing) } } } diff --git a/iOSClient/Media/NCMediaRowViewModel.swift b/iOSClient/Media/NCMediaRowViewModel.swift index d604d1da63..ee8671a1bc 100644 --- a/iOSClient/Media/NCMediaRowViewModel.swift +++ b/iOSClient/Media/NCMediaRowViewModel.swift @@ -8,6 +8,7 @@ import Foundation import NextcloudKit +import Queuer struct RowData { var scaledThumbnails: [ScaledThumbnail] = [] @@ -21,7 +22,7 @@ struct ScaledThumbnail: Hashable { let metadata: tableMetadata func hash(into hasher: inout Hasher) { - hasher.combine(image) + hasher.combine(metadata.ocId) } } @@ -30,6 +31,13 @@ struct ScaledThumbnail: Hashable { private var metadatas: [tableMetadata] = [] + internal let cache = NCMediaCache.shared + +// internal lazy var cache = manager.cache +// internal lazy var thumbnailsQueue = manager.queuer + + var operations: [ConcurrentOperation] = [] + func configure(metadatas: [tableMetadata]) { self.metadatas = metadatas } @@ -37,23 +45,24 @@ struct ScaledThumbnail: Hashable { func downloadThumbnails(rowWidth: CGFloat, spacing: CGFloat) { var thumbnails: [ScaledThumbnail] = [] - metadatas.enumerated().forEach { index, metadata in - let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag) + metadatas.forEach { metadata in + if let cachedImage = cache.getImage(ocId: metadata.ocId) { + let thumbnail = ScaledThumbnail(image: cachedImage, metadata: metadata) + thumbnails.append(thumbnail) - if let thumbnailPath, FileManager.default.fileExists(atPath: thumbnailPath) { + DispatchQueue.main.async { + self.calculateShrinkRatio(thumbnails: &thumbnails, rowWidth: rowWidth, spacing: spacing) + } + } else if let thumbnailPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag), 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: rowWidth, spacing: spacing) + let thumbnail = ScaledThumbnail(image: image, metadata: metadata) + cache.setImage(ocId: metadata.ocId, image: image) +// cache.setValue(thumbnail, forKey: metadata.ocId) + thumbnails.append(thumbnail) - rowData.scaledThumbnails = thumbnails - rowData.shrinkRatio = shrinkRatio + DispatchQueue.main.async { + self.calculateShrinkRatio(thumbnails: &thumbnails, rowWidth: rowWidth, spacing: spacing) } } } else { @@ -67,41 +76,61 @@ struct ScaledThumbnail: Hashable { } 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 - print(metadata.isVideo.description + " " + metadata.fileName) - if error == .success, let image = imageIcon { +// let concurrentOperation = ConcurrentOperation { _ in + 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 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), isPlaceholderImage: true, metadata: metadata)) - } - 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 thumbnail: ScaledThumbnail + + if error == .success, let image = imageIcon { + thumbnail = ScaledThumbnail(image: image, metadata: metadata) + self.cache.setImage(ocId: metadata.ocId, image: image) + } else { + thumbnail = ScaledThumbnail(image: UIImage(systemName: metadata.isVideo ? "video.fill" : "photo.fill")!.withRenderingMode(.alwaysTemplate), isPlaceholderImage: true, metadata: metadata) + } - let shrinkRatio = self.getShrinkRatio(thumbnailsInRow: thumbnails, fullWidth: rowWidth, spacing: spacing) + thumbnails.append(thumbnail) +// self.cache.setValue(thumbnail, forKey: metadata.ocId) - self.rowData.scaledThumbnails = thumbnails - self.rowData.shrinkRatio = shrinkRatio + DispatchQueue.main.async { + self.calculateShrinkRatio(thumbnails: &thumbnails, rowWidth: rowWidth, spacing: spacing) } } - } +// } + +// operations.append(concurrentOperation) +// thumbnailsQueue.addOperation(concurrentOperation) + } + } + } + + private func calculateShrinkRatio(thumbnails: inout [ScaledThumbnail], rowWidth: CGFloat, spacing: CGFloat) { + 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) + + self.rowData.scaledThumbnails = thumbnails + self.rowData.shrinkRatio = shrinkRatio } } + + func cancelDownloadingThumbnails() { + operations.forEach {( $0.cancel() )} + operations.removeAll() } - func getScaledThumbnailSize(of thumbnail: ScaledThumbnail, thumbnailsInRow thumbnails: [ScaledThumbnail]) -> CGSize { + private 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 @@ -114,7 +143,7 @@ struct ScaledThumbnail: Hashable { return .init(width: newWidth, height: newHeight) } - func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat, spacing: CGFloat) -> CGFloat { + private func getShrinkRatio(thumbnailsInRow thumbnails: [ScaledThumbnail], fullWidth: CGFloat, spacing: CGFloat) -> CGFloat { var newSummedWidth: CGFloat = 0 for thumbnail in thumbnails { diff --git a/iOSClient/Media/NCMediaScrollView.swift b/iOSClient/Media/NCMediaScrollView.swift new file mode 100644 index 0000000000..ace9946cbc --- /dev/null +++ b/iOSClient/Media/NCMediaScrollView.swift @@ -0,0 +1,120 @@ +// +// NCMediaScrollView.swift +// Nextcloud +// +// Created by Milen on 13.10.23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +import SwiftUI + +struct NCMediaScrollView: View, Equatable { + static func == (lhs: NCMediaScrollView, rhs: NCMediaScrollView) -> Bool { + return lhs.metadatas == rhs.metadatas + } + + var metadatas: [[tableMetadata]] + @EnvironmentObject var vm: NCMediaViewModel + @EnvironmentObject var parent: NCMediaUIKitWrapper + @Binding var isInSelectMode: Bool + @Binding var selectedMetadatas: [tableMetadata] + @Binding var columnCountStages: [Int] + @Binding var columnCountStagesIndex: Int + @Binding var title: String + + var body: some View { + let _ = Self._printChanges() + + List { + Spacer(minLength: 50).listRowSeparator(.hidden) + + // LazyVStack(alignment: .leading, spacing: 2) { + ForEach(metadatas, id: \.self) { rowMetadatas in + NCMediaRow(metadatas: rowMetadatas, isInSelectMode: $isInSelectMode) { tappedThumbnail, isSelected in + if isInSelectMode, isSelected { + selectedMetadatas.append(tappedThumbnail.metadata) + } else { + selectedMetadatas.removeAll(where: { $0.ocId == tappedThumbnail.metadata.ocId }) + } + + if !isInSelectMode { + let selectedMetadata = tappedThumbnail.metadata + 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 .addToFavorites: + vm.addToFavorites(metadata: selectedMetadata) + case .details: + 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) + } + } + .equatable() + .onAppear { + // title = CCUtility.getTitleSectionDate(rowMetadatas.first?.date as? Date) ?? "" + } + .listRowSeparator(.hidden) + .listRowSpacing(0) + .listRowInsets(.init(top: 2, leading: 0, bottom: 0, trailing: 0)) + } + + // TODO: 3. Here we load old media (happens immediately since progress view appears in the beginning, should fix) + if vm.needsLoadingMoreItems { + ProgressView() + .frame(maxWidth: .infinity) + .onAppear { vm.loadMoreItems() } + .padding(.top, 10) + } + + Spacer(minLength: 40).listRowSeparator(.hidden) + }.background(GeometryReader { geometry in + Color.clear + .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) + }) + .listStyle(.plain) + // .padding(.top, 70) + // .padding(.bottom, 40) + // } + .coordinateSpace(name: "scroll") + .refreshable { + await vm.onPullToRefresh() + } + // .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in + // isScrolledToTop = value.y >= 40 + // } + // 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: scrollView.superview!.topAnchor, constant: 120).isActive = true + // scrollView.refreshControl?.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true + // + // if offsetPublisherSubscription == nil { + // offsetPublisherSubscription = scrollView.publisher(for: \.contentOffset) + // .sink { offset in + // isScrolledToTop = offset.y <= 10 + // + //// withAnimation(.easeInOut) { + //// titleColor = isScrolledToTop ? Color.primary : .white + //// toolbarItemsColor = isScrolledToTop ? .blue : .white + //// toolbarColors = isScrolledToTop ? [.clear] : [.black.opacity(0.8), .black.opacity(0.0)] + //// } + // } + // } + // } + // .preference(key: TitlePreferenceKey.self, value: title) + } +} diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EE.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EE.swift index 0cd26fd42f..d5b724b3af 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EE.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EE.swift @@ -23,6 +23,10 @@ import Foundation import NextcloudKit class NCNetworkingE2EE: NSObject { + public static let shared: NCNetworkingE2EE = { + let instance = NCNetworkingE2EE() + return instance + }() func isInUpload(account: String, serverUrl: String) -> Bool { @@ -187,7 +191,7 @@ class NCNetworkingE2EE: NSObject { func unlockAll(account: String) { - guard NCKeychain().isEndToEndEnabled(account: account) else { return } + guard CCUtility.isEnd(toEndEnabled: account) else { return } Task { for result in NCManageDatabase.shared.getE2EAllTokenLock(account: account) { diff --git a/iOSClient/Supporting Files/hu.lproj/Localizable.strings b/iOSClient/Supporting Files/hu.lproj/Localizable.strings index 25abd56bce006815a79fbbb1b5461c047aeeeafc..bfea7e6e6cb46f415a85cc7771ffefd03b3fb05e 100644 GIT binary patch delta 106 zcmX?bkA2-;_6_TzCbPsuHP4FPJ}a7$$zL;sA%h{4p_oB|A(tVMp%O?JF{Cn-0dXQj u4p=lFETS;^VW+sf0#G!GA)ld?p#&&b0@Rxdqziy_K9HBc{r4(HR~G<%Qygpn delta 250 zcmZ2?m;Jy!_6_TzRFxRw8Oj+F88R74fUGniox_j{WEC(J0qFvuOc6uk^nPVV(VzgZ zR3bw$P(lH&Ne9R-1uD#8$OkG;0rFsmC;$!11M*Rf0$P&^R0$GSQ<$6+ozTn?y`3YP zk;y;am7xkKS^|_UV|d7r1caGDKFEwTpjZ(@E>JWX$p6fc4Ya8Qto9*LJOj#01G*~* dD4GbwX$&uc`YM4cUxMuqWX#!~xr))%1pp`dIQ;+s diff --git a/iOSClient/Utility/PreferenceKeys.swift b/iOSClient/Utility/PreferenceKeys.swift index 5ddb42b507..433a567ee8 100644 --- a/iOSClient/Utility/PreferenceKeys.swift +++ b/iOSClient/Utility/PreferenceKeys.swift @@ -14,3 +14,9 @@ struct ScrollOffsetPreferenceKey: PreferenceKey { static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {} } + +struct TitlePreferenceKey: PreferenceKey { + static var defaultValue: String = "" + + static func reduce(value: inout String, nextValue: () -> String) {} +}