From 84fa7fd1367fc9ade03b50029c43284eceec7004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 3 Sep 2024 18:03:12 +0200 Subject: [PATCH 1/8] feat: Can natively show a public share within the app --- kDrive/AppRouter.swift | 4 +- .../UI/Controller/Files/FilePresenter.swift | 5 +- .../PublicShareViewModel.swift} | 40 ++------------ .../Menu/Share/SharedWithMeViewModel.swift | 53 +++++++++++++++++++ kDrive/Utils/UniversalLinksHelper.swift | 9 +++- kDriveCore/Data/Api/Endpoint+Share.swift | 2 +- kDriveCore/Data/Cache/AccountManager.swift | 19 +++++-- .../DriveFileManager/DriveFileManager.swift | 15 ++++++ .../DriveInfosManager/DriveInfosManager.swift | 8 +++ kDriveCore/Data/Models/Drive/Drive.swift | 3 +- kDriveCore/Utils/AppNavigable.swift | 4 +- 11 files changed, 112 insertions(+), 50 deletions(-) rename kDrive/UI/Controller/Menu/{SharedWithMe/SharedWithMeViewModel.swift => Share/PublicShareViewModel.swift} (63%) create mode 100644 kDrive/UI/Controller/Menu/Share/SharedWithMeViewModel.swift diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index ad648d76d..6c0f38556 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -585,7 +585,7 @@ public struct AppRouter: AppNavigable { // MARK: RouterFileNavigable @MainActor public func presentPublicShare( - rootFolder: File, + frozenRootFolder: File, publicShareProxy: PublicShareProxy, driveFileManager: DriveFileManager, apiFetcher: PublicShareApiFetcher @@ -598,7 +598,7 @@ public struct AppRouter: AppNavigable { let filePresenter = FilePresenter(viewController: rootViewController) filePresenter.presentPublicShareDirectory(publicShareProxy: publicShareProxy, - rootFolder: rootFolder, + frozenRootFolder: frozenRootFolder, rootViewController: rootViewController, driveFileManager: driveFileManager, apiFetcher: apiFetcher) diff --git a/kDrive/UI/Controller/Files/FilePresenter.swift b/kDrive/UI/Controller/Files/FilePresenter.swift index 7dbba6db7..d2b50c691 100644 --- a/kDrive/UI/Controller/Files/FilePresenter.swift +++ b/kDrive/UI/Controller/Files/FilePresenter.swift @@ -134,7 +134,7 @@ final class FilePresenter { public func presentPublicShareDirectory( publicShareProxy: PublicShareProxy, - rootFolder: File, + frozenRootFolder: File, rootViewController: UIViewController, driveFileManager: DriveFileManager, apiFetcher: PublicShareApiFetcher @@ -142,7 +142,7 @@ final class FilePresenter { let viewModel = PublicShareViewModel(publicShareProxy: publicShareProxy, sortType: .nameAZ, driveFileManager: driveFileManager, - currentDirectory: rootFolder, + currentDirectory: frozenRootFolder, apiFetcher: apiFetcher) // TODO: Fix access right @@ -150,6 +150,7 @@ final class FilePresenter { // return // } + // TODO: Build clean context aware navigation let nextVC = FileListViewController(viewModel: viewModel) print("nextVC:\(nextVC) viewModel:\(viewModel) navigationController:\(navigationController)") // navigationController?.pushViewController(nextVC, animated: true) diff --git a/kDrive/UI/Controller/Menu/SharedWithMe/SharedWithMeViewModel.swift b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift similarity index 63% rename from kDrive/UI/Controller/Menu/SharedWithMe/SharedWithMeViewModel.swift rename to kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift index 3410a6a1c..0898ce34d 100644 --- a/kDrive/UI/Controller/Menu/SharedWithMe/SharedWithMeViewModel.swift +++ b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift @@ -1,6 +1,6 @@ /* Infomaniak kDrive - iOS App - Copyright (C) 2021 Infomaniak Network SA + Copyright (C) 2024 Infomaniak Network SA 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 @@ -28,9 +28,10 @@ final class PublicShareViewModel: InMemoryFileListViewModel { required init(driveFileManager: DriveFileManager, currentDirectory: File? = nil) { guard let currentDirectory else { - fatalError("woops") + fatalError("PublicShareViewModel requires a currentDirectory to work") } + // TODO: i18n let configuration = Configuration(selectAllSupported: false, rootTitle: "public share", emptyViewType: .emptyFolder, @@ -40,7 +41,6 @@ final class PublicShareViewModel: InMemoryFileListViewModel { rootProxy = currentDirectory.proxify() super.init(configuration: configuration, driveFileManager: driveFileManager, currentDirectory: currentDirectory) observedFiles = AnyRealmCollection(currentDirectory.children) - print("• observedFiles :\(observedFiles.count)") } convenience init( @@ -57,7 +57,6 @@ final class PublicShareViewModel: InMemoryFileListViewModel { } override func loadFiles(cursor: String? = nil, forceRefresh: Bool = false) async throws { - print("• loadFiles:\(cursor):\(forceRefresh)") guard !isLoading || cursor != nil, let publicShareProxy, let publicShareApiFetcher else { @@ -75,39 +74,6 @@ final class PublicShareViewModel: InMemoryFileListViewModel { let (_, nextCursor) = try await driveFileManager.publicShareFiles(rootProxy: rootProxy, publicShareProxy: publicShareProxy, publicShareApiFetcher: publicShareApiFetcher) - print("• nextCursor:\(nextCursor)") - endRefreshing() - if let nextCursor { - try await loadFiles(cursor: nextCursor) - } - } -} - -class SharedWithMeViewModel: FileListViewModel { - required init(driveFileManager: DriveFileManager, currentDirectory: File? = nil) { - let sharedWithMeRootFile = driveFileManager.getManagedFile(from: DriveFileManager.sharedWithMeRootFile) - let configuration = Configuration(selectAllSupported: false, - rootTitle: KDriveCoreStrings.Localizable.sharedWithMeTitle, - emptyViewType: .noSharedWithMe, - supportsDrop: false, - matomoViewPath: [MatomoUtils.Views.menu.displayName, "SharedWithMe"]) - - super.init(configuration: configuration, driveFileManager: driveFileManager, currentDirectory: sharedWithMeRootFile) - observedFiles = AnyRealmCollection(AnyRealmCollection(sharedWithMeRootFile.children).filesSorted(by: sortType)) - } - - override func loadFiles(cursor: String? = nil, forceRefresh: Bool = false) async throws { - guard !isLoading || cursor != nil else { return } - - // Only show loading indicator if we have nothing in cache - if !currentDirectory.canLoadChildrenFromCache { - startRefreshing(cursor: cursor) - } - defer { - endRefreshing() - } - - let (_, nextCursor) = try await driveFileManager.sharedWithMeFiles(cursor: cursor, sortType: sortType, forceRefresh: true) endRefreshing() if let nextCursor { try await loadFiles(cursor: nextCursor) diff --git a/kDrive/UI/Controller/Menu/Share/SharedWithMeViewModel.swift b/kDrive/UI/Controller/Menu/Share/SharedWithMeViewModel.swift new file mode 100644 index 000000000..0f8a41ce3 --- /dev/null +++ b/kDrive/UI/Controller/Menu/Share/SharedWithMeViewModel.swift @@ -0,0 +1,53 @@ +/* + Infomaniak kDrive - iOS App + Copyright (C) 2021 Infomaniak Network SA + + 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 kDriveCore +import RealmSwift +import UIKit + +class SharedWithMeViewModel: FileListViewModel { + required init(driveFileManager: DriveFileManager, currentDirectory: File? = nil) { + let sharedWithMeRootFile = driveFileManager.getManagedFile(from: DriveFileManager.sharedWithMeRootFile) + let configuration = Configuration(selectAllSupported: false, + rootTitle: KDriveCoreStrings.Localizable.sharedWithMeTitle, + emptyViewType: .noSharedWithMe, + supportsDrop: false, + matomoViewPath: [MatomoUtils.Views.menu.displayName, "SharedWithMe"]) + + super.init(configuration: configuration, driveFileManager: driveFileManager, currentDirectory: sharedWithMeRootFile) + observedFiles = AnyRealmCollection(AnyRealmCollection(sharedWithMeRootFile.children).filesSorted(by: sortType)) + } + + override func loadFiles(cursor: String? = nil, forceRefresh: Bool = false) async throws { + guard !isLoading || cursor != nil else { return } + + // Only show loading indicator if we have nothing in cache + if !currentDirectory.canLoadChildrenFromCache { + startRefreshing(cursor: cursor) + } + defer { + endRefreshing() + } + + let (_, nextCursor) = try await driveFileManager.sharedWithMeFiles(cursor: cursor, sortType: sortType, forceRefresh: true) + endRefreshing() + if let nextCursor { + try await loadFiles(cursor: nextCursor) + } + } +} diff --git a/kDrive/Utils/UniversalLinksHelper.swift b/kDrive/Utils/UniversalLinksHelper.swift index 32e7734a1..3c8ea1dd0 100644 --- a/kDrive/Utils/UniversalLinksHelper.swift +++ b/kDrive/Utils/UniversalLinksHelper.swift @@ -138,10 +138,17 @@ enum UniversalLinksHelper { let rootFolder = try await apiFetcher.getShareLinkFile(driveId: driveId, linkUuid: linkUuid, fileId: fileId) + // Root folder must be in database for the FileListViewModel to work + try driveFileManager.database.writeTransaction { writableRealm in + writableRealm.add(rootFolder) + } + + let frozenRootFolder = rootFolder.freeze() + @InjectService var appNavigable: AppNavigable let publicShareProxy = PublicShareProxy(driveId: driveId, fileId: fileId, shareLinkUid: linkUuid) await appNavigable.presentPublicShare( - rootFolder: rootFolder, + frozenRootFolder: frozenRootFolder, publicShareProxy: publicShareProxy, driveFileManager: driveFileManager, apiFetcher: apiFetcher diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index 21b5d4e4b..765077eb3 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -62,7 +62,7 @@ public extension Endpoint { let withQuery = URLQueryItem(name: "with", value: "capabilities,conversion_capabilities,supported_by") let shareLinkQueryItems = [orderByQuery, orderQuery, withQuery] - let fileChildrenEndpoint = Self.shareUrlV3.appending(path: "/\(driveId)/share/\(linkUuid)/files") + let fileChildrenEndpoint = Self.shareUrlV3.appending(path: "/\(driveId)/share/\(linkUuid)/files/\(fileId)/files") return fileChildrenEndpoint.appending(path: "", queryItems: shareLinkQueryItems) } diff --git a/kDriveCore/Data/Cache/AccountManager.swift b/kDriveCore/Data/Cache/AccountManager.swift index f58e1603e..3f81233e7 100644 --- a/kDriveCore/Data/Cache/AccountManager.swift +++ b/kDriveCore/Data/Cache/AccountManager.swift @@ -216,15 +216,26 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { return inMemoryDriveFileManager } - // Big hack, refactor to allow for non authenticated requests + // TODO: Big hack, refactor to allow for non authenticated requests guard let someToken = apiFetchers.values.first?.currentToken else { - fatalError("probably no account availlable") + fatalError("probably no account available") } + // FileViewModel K.O. without a valid drive in Realm, therefore add one + let publicShareDrive = Drive() + publicShareDrive.objectId = publicShareId + @LazyInjectService var driveInfosManager: DriveInfosManager + do { + try driveInfosManager.storePublicShareDrive(drive: publicShareDrive) + } catch { + fatalError("unable to update public share drive in base, \(error)") + } + let forzenPublicShareDrive = publicShareDrive.freeze() + let apiFetcher = DriveApiFetcher(token: someToken, delegate: SomeRefreshTokenDelegate()) let context = DriveFileManagerContext.publicShare(shareId: publicShareId) - let noopDrive = Drive() - return DriveFileManager(drive: noopDrive, apiFetcher: apiFetcher, context: context) + + return DriveFileManager(drive: forzenPublicShareDrive, apiFetcher: apiFetcher, context: context) } public func getFirstAvailableDriveFileManager(for userId: Int) throws -> DriveFileManager { diff --git a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift index f66d4ec21..c022ca1a7 100644 --- a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift +++ b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift @@ -26,6 +26,21 @@ import InfomaniakLogin import RealmSwift import SwiftRegex +// TODO: Move to core +extension TransactionExecutor: CustomStringConvertible { + public var description: String { + var render = "TransactionExecutor: realm access issue" + try? writeTransaction { realm in + render = """ + TransactionExecutor: + realmURL:\(realm.configuration.fileURL) + inMemory:\(realm.configuration.inMemoryIdentifier) + """ + } + return render + } +} + // MARK: - Transactionable public final class DriveFileManager { diff --git a/kDriveCore/Data/Cache/DriveInfosManager/DriveInfosManager.swift b/kDriveCore/Data/Cache/DriveInfosManager/DriveInfosManager.swift index 744bf2232..aeee624b1 100644 --- a/kDriveCore/Data/Cache/DriveInfosManager/DriveInfosManager.swift +++ b/kDriveCore/Data/Cache/DriveInfosManager/DriveInfosManager.swift @@ -134,6 +134,14 @@ public final class DriveInfosManager: DriveInfosManagerQueryable { drive.sharedWithMe = sharedWithMe } + // TODO: Add a flag that this drive can be cleaned + /// Store a specific public share Drive in realm for use by FileListViewControllers + public func storePublicShareDrive(drive: Drive) throws { + try driveInfoDatabase.writeTransaction { writableRealm in + writableRealm.add(drive, update: .modified) + } + } + @discardableResult func storeDriveResponse(user: InfomaniakCore.UserProfile, driveResponse: DriveResponse) -> [Drive] { var driveList = [Drive]() diff --git a/kDriveCore/Data/Models/Drive/Drive.swift b/kDriveCore/Data/Models/Drive/Drive.swift index c6ff0c515..d6d8bbef9 100644 --- a/kDriveCore/Data/Models/Drive/Drive.swift +++ b/kDriveCore/Data/Models/Drive/Drive.swift @@ -190,8 +190,9 @@ public final class Drive: Object, Codable { // File is not managed by Realm: cannot use the `.sorted(by:)` method :( fileCategoriesIds = file.categories.sorted { $0.addedAt.compare($1.addedAt) == .orderedAscending }.map(\.categoryId) } - let filteredCategories = categories.filter(NSPredicate(format: "id IN %@", fileCategoriesIds)) + // Sort the categories + let filteredCategories = categories.filter("id IN %@", fileCategoriesIds) return fileCategoriesIds.compactMap { id in filteredCategories.first { $0.id == id } } } diff --git a/kDriveCore/Utils/AppNavigable.swift b/kDriveCore/Utils/AppNavigable.swift index 7a002b42f..bcd52b59e 100644 --- a/kDriveCore/Utils/AppNavigable.swift +++ b/kDriveCore/Utils/AppNavigable.swift @@ -64,9 +64,9 @@ public protocol RouterFileNavigable { /// - office: Open in only office @MainActor func present(file: File, driveFileManager: DriveFileManager, office: Bool) - /// Present a file list for a public share + /// Present a file list for a public share, regardless of authenticated state @MainActor func presentPublicShare( - rootFolder: File, + frozenRootFolder: File, publicShareProxy: PublicShareProxy, driveFileManager: DriveFileManager, apiFetcher: PublicShareApiFetcher From 130305f6e2605a6c55ac8cead0f93bd18754a188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Mon, 16 Sep 2024 16:52:18 +0200 Subject: [PATCH 2/8] fix(FilePresenter): Present public share in context --- kDrive/AppRouter.swift | 36 +++++++++++++++---- .../UI/Controller/Files/FilePresenter.swift | 26 -------------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 6c0f38556..d5ddfe1d5 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -284,7 +284,8 @@ public struct AppRouter: AppNavigable { let fileIds = sceneUserInfo[SceneRestorationValues.Carousel.filesIds.rawValue] as? [Int], let currentIndex = sceneUserInfo[SceneRestorationValues.Carousel.currentIndex.rawValue] as? Int, let normalFolderHierarchy = sceneUserInfo[SceneRestorationValues.Carousel.normalFolderHierarchy.rawValue] as? Bool, - let presentationOrigin = sceneUserInfo[SceneRestorationValues.Carousel.presentationOrigin.rawValue] as? PresentationOrigin else { + let presentationOrigin = + sceneUserInfo[SceneRestorationValues.Carousel.presentationOrigin.rawValue] as? PresentationOrigin else { Log.sceneDelegate("metadata issue for PreviewController :\(sceneUserInfo)", level: .error) return } @@ -596,12 +597,33 @@ public struct AppRouter: AppNavigable { fatalError("TODO: lazy load a rootViewController") } - let filePresenter = FilePresenter(viewController: rootViewController) - filePresenter.presentPublicShareDirectory(publicShareProxy: publicShareProxy, - frozenRootFolder: frozenRootFolder, - rootViewController: rootViewController, - driveFileManager: driveFileManager, - apiFetcher: apiFetcher) + guard let rootViewController = window.rootViewController as? MainTabViewController else { + fatalError("Root is not a MainTabViewController") + return + } + + // TODO: Fix access right + // guard !rootFolder.isDisabled else { + // return + // } + + rootViewController.dismiss(animated: false) { + rootViewController.selectedIndex = MainTabBarIndex.files.rawValue + + guard let navigationController = rootViewController.selectedViewController as? UINavigationController else { + return + } + + let viewModel = PublicShareViewModel(publicShareProxy: publicShareProxy, + sortType: .nameAZ, + driveFileManager: driveFileManager, + currentDirectory: frozenRootFolder, + apiFetcher: apiFetcher) + let viewController = FileListViewController(viewModel: viewModel) + print("viewController:\(viewController) viewModel:\(viewModel) navigationController:\(navigationController)") + + navigationController.pushViewController(viewController, animated: true) + } } @MainActor public func present(file: File, driveFileManager: DriveFileManager) { diff --git a/kDrive/UI/Controller/Files/FilePresenter.swift b/kDrive/UI/Controller/Files/FilePresenter.swift index d2b50c691..0d398e815 100644 --- a/kDrive/UI/Controller/Files/FilePresenter.swift +++ b/kDrive/UI/Controller/Files/FilePresenter.swift @@ -132,32 +132,6 @@ final class FilePresenter { } } - public func presentPublicShareDirectory( - publicShareProxy: PublicShareProxy, - frozenRootFolder: File, - rootViewController: UIViewController, - driveFileManager: DriveFileManager, - apiFetcher: PublicShareApiFetcher - ) { - let viewModel = PublicShareViewModel(publicShareProxy: publicShareProxy, - sortType: .nameAZ, - driveFileManager: driveFileManager, - currentDirectory: frozenRootFolder, - apiFetcher: apiFetcher) - - // TODO: Fix access right -// guard !rootFolder.isDisabled else { -// return -// } - - // TODO: Build clean context aware navigation - let nextVC = FileListViewController(viewModel: viewModel) - print("nextVC:\(nextVC) viewModel:\(viewModel) navigationController:\(navigationController)") -// navigationController?.pushViewController(nextVC, animated: true) - - rootViewController.present(nextVC, animated: true) - } - public func presentDirectory( for file: File, driveFileManager: DriveFileManager, From 0b48b91fb835c2a5afd63b4fe9ff40c0fe22a0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Mon, 16 Sep 2024 17:44:26 +0200 Subject: [PATCH 3/8] fix: Capabilities for Public Share --- kDrive/AppRouter.swift | 7 ++++--- kDriveCore/Data/Api/PublicShareApiFetcher.swift | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index d5ddfe1d5..66a8b000f 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -603,9 +603,10 @@ public struct AppRouter: AppNavigable { } // TODO: Fix access right - // guard !rootFolder.isDisabled else { - // return - // } + guard !frozenRootFolder.isDisabled else { + fatalError("isDisabled") + return + } rootViewController.dismiss(animated: false) { rootViewController.selectedIndex = MainTabBarIndex.files.rawValue diff --git a/kDriveCore/Data/Api/PublicShareApiFetcher.swift b/kDriveCore/Data/Api/PublicShareApiFetcher.swift index 5804f362b..789de315e 100644 --- a/kDriveCore/Data/Api/PublicShareApiFetcher.swift +++ b/kDriveCore/Data/Api/PublicShareApiFetcher.swift @@ -36,7 +36,10 @@ public class PublicShareApiFetcher: ApiFetcher { public func getShareLinkFile(driveId: Int, linkUuid: String, fileId: Int) async throws -> File { let shareLinkFileUrl = Endpoint.shareLinkFile(driveId: driveId, linkUuid: linkUuid, fileId: fileId).url - let request = Session.default.request(shareLinkFileUrl) + let requestParameters: [String: String] = [ + APIUploadParameter.with.rawValue: FileWith.capabilities.rawValue + ] + let request = Session.default.request(shareLinkFileUrl, parameters: requestParameters) let shareLinkFile: File = try await perform(request: request) return shareLinkFile } From 272968e6e19cca68bbf70f966cb1094f8bcdbabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Tue, 17 Sep 2024 17:11:22 +0200 Subject: [PATCH 4/8] feat: Thumbnails on public share --- .../View/Files/FileCollectionViewCell.swift | 82 +++++++++++++++---- .../Files/FileGridCollectionViewCell.swift | 7 +- kDrive/Utils/UniversalLinksHelper.swift | 6 +- kDriveCore/Data/Api/Endpoint+Share.swift | 10 ++- kDriveCore/Data/Cache/AccountManager.swift | 9 +- .../DriveFileManager+Transactionable.swift | 2 +- .../DriveFileManager/DriveFileManager.swift | 44 ++++++++++ kDriveCore/Data/Models/File+Image.swift | 32 ++++++++ 8 files changed, 165 insertions(+), 27 deletions(-) diff --git a/kDrive/UI/View/Files/FileCollectionViewCell.swift b/kDrive/UI/View/Files/FileCollectionViewCell.swift index 23901692f..e4172dd71 100644 --- a/kDrive/UI/View/Files/FileCollectionViewCell.swift +++ b/kDrive/UI/View/Files/FileCollectionViewCell.swift @@ -48,6 +48,16 @@ protocol FileCellDelegate: AnyObject { var file: File var selectionMode: Bool var isSelected = false + + /// UUID of the public share if file exists within a public share + let publicShareId: String? + + /// Drive ID of the public share if file exists within a public share + let publicDriveId: Int? + + /// Root file ID of the public share if file exists within a public share + let publicRootFileId: Int? + private var downloadProgressObserver: ObservationToken? private var downloadObserver: ObservationToken? var thumbnailDownloadTask: Kingfisher.DownloadTask? @@ -114,6 +124,10 @@ protocol FileCellDelegate: AnyObject { init(driveFileManager: DriveFileManager, file: File, selectionMode: Bool) { self.file = file self.selectionMode = selectionMode + publicShareId = driveFileManager.publicShareId + publicDriveId = driveFileManager.publicDriveId + publicRootFileId = driveFileManager.publicRootFileId + categories = driveFileManager.drive.categories(for: file) } @@ -138,26 +152,53 @@ protocol FileCellDelegate: AnyObject { } func setThumbnail(on imageView: UIImageView) { + // check if public share / use specific endpoint guard !file.isInvalidated, - (file.convertedType == .image || file.convertedType == .video) && file.supportedBy.contains(.thumbnail) - else { return } + (file.convertedType == .image || file.convertedType == .video) && file.supportedBy.contains(.thumbnail) else { + return + } + // Configure placeholder imageView.image = nil imageView.contentMode = .scaleAspectFill imageView.layer.cornerRadius = UIConstants.imageCornerRadius imageView.layer.masksToBounds = true imageView.backgroundColor = KDriveResourcesAsset.loaderDefaultColor.color - // Fetch thumbnail - thumbnailDownloadTask = file.getThumbnail { [requestFileId = file.id, weak self] image, _ in - guard let self, - !self.file.isInvalidated, - !self.isSelected else { - return + + if let publicShareId = publicShareId, + let publicDriveId = publicDriveId { + // Fetch public share thumbnail + thumbnailDownloadTask = file.getPublicShareThumbnail(publicShareId: publicShareId, + publicDriveId: publicDriveId, + publicFileId: file.id) { [ + requestFileId = file.id, + weak self + ] image, _ in + guard let self, + !self.file.isInvalidated, + !self.isSelected else { + return + } + + if file.id == requestFileId { + imageView.image = image + imageView.backgroundColor = nil + } } - if file.id == requestFileId { - imageView.image = image - imageView.backgroundColor = nil + } else { + // Fetch thumbnail + thumbnailDownloadTask = file.getThumbnail { [requestFileId = file.id, weak self] image, _ in + guard let self, + !self.file.isInvalidated, + !self.isSelected else { + return + } + + if file.id == requestFileId { + imageView.image = image + imageView.backgroundColor = nil + } } } } @@ -302,7 +343,7 @@ class FileCollectionViewCell: UICollectionViewCell, SwipableCell { func configure(with viewModel: FileViewModel) { self.viewModel = viewModel - configureLogoImage() + configureLogoImage(viewModel: viewModel) titleLabel.text = viewModel.title detailLabel?.text = viewModel.subtitle favoriteImageView?.isHidden = !viewModel.isFavorite @@ -321,7 +362,12 @@ class FileCollectionViewCell: UICollectionViewCell, SwipableCell { } func configureWith(driveFileManager: DriveFileManager, file: File, selectionMode: Bool = false) { - configure(with: FileViewModel(driveFileManager: driveFileManager, file: file, selectionMode: selectionMode)) + let fileViewModel = FileViewModel( + driveFileManager: driveFileManager, + file: file, + selectionMode: selectionMode + ) + configure(with: fileViewModel) } /// Update the cell selection mode. @@ -333,18 +379,20 @@ class FileCollectionViewCell: UICollectionViewCell, SwipableCell { } func configureForSelection() { - guard viewModel?.selectionMode == true else { return } + guard let viewModel, + viewModel.selectionMode == true else { + return + } if isSelected { configureCheckmarkImage() configureImport(shouldDisplay: false) } else { - configureLogoImage() + configureLogoImage(viewModel: viewModel) } } - private func configureLogoImage() { - guard let viewModel else { return } + private func configureLogoImage(viewModel: FileViewModel) { logoImage.isAccessibilityElement = true logoImage.accessibilityLabel = viewModel.iconAccessibilityLabel logoImage.image = viewModel.icon diff --git a/kDrive/UI/View/Files/FileGridCollectionViewCell.swift b/kDrive/UI/View/Files/FileGridCollectionViewCell.swift index 4ba0608a7..58b6e7ec2 100644 --- a/kDrive/UI/View/Files/FileGridCollectionViewCell.swift +++ b/kDrive/UI/View/Files/FileGridCollectionViewCell.swift @@ -110,7 +110,12 @@ class FileGridCollectionViewCell: FileCollectionViewCell { } override func configureWith(driveFileManager: DriveFileManager, file: File, selectionMode: Bool = false) { - configure(with: FileGridViewModel(driveFileManager: driveFileManager, file: file, selectionMode: selectionMode)) + let viewModel = FileGridViewModel( + driveFileManager: driveFileManager, + file: file, + selectionMode: selectionMode + ) + configure(with: viewModel) } override func configureLoading() { diff --git a/kDrive/Utils/UniversalLinksHelper.swift b/kDrive/Utils/UniversalLinksHelper.swift index 3c8ea1dd0..38c72c2cf 100644 --- a/kDrive/Utils/UniversalLinksHelper.swift +++ b/kDrive/Utils/UniversalLinksHelper.swift @@ -102,7 +102,11 @@ enum UniversalLinksHelper { } // get file ID from metadata - let publicShareDriveFileManager = accountManager.getInMemoryDriveFileManager(for: shareLinkUid) + let publicShareDriveFileManager = accountManager.getInMemoryDriveFileManager( + for: shareLinkUid, + driveId: driveIdInt, + rootFileId: metadata.fileId + ) openPublicShare(driveId: driveIdInt, linkUuid: shareLinkUid, fileId: metadata.fileId, diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index 765077eb3..dfb913047 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -23,7 +23,6 @@ import RealmSwift // MARK: - Share Links public extension Endpoint { - /// It is necessary to keep V1 here for backward compatibility of old links static var shareUrlV1: Endpoint { return Endpoint(hostKeypath: \.driveHost, path: "/app") @@ -52,7 +51,12 @@ public extension Endpoint { /// Share link file static func shareLinkFile(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { - Self.shareUrlV3.appending(path: "/\(driveId)/share/\(linkUuid)/files/\(fileId)") + shareUrlV3.appending(path: "/\(driveId)/share/\(linkUuid)/files/\(fileId)") + } + + /// Some legacy calls like thumbnails require a V2 call + static func shareLinkFileV2(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { + shareUrlV2.appending(path: "/\(driveId)/share/\(linkUuid)/files/\(fileId)") } /// Share link file children @@ -68,7 +72,7 @@ public extension Endpoint { /// Share link file thumbnail static func shareLinkFileThumbnail(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { - return shareLinkFile(driveId: driveId, linkUuid: linkUuid, fileId: fileId).appending(path: "/thumbnail") + return shareLinkFileV2(driveId: driveId, linkUuid: linkUuid, fileId: fileId).appending(path: "/thumbnail") } /// Share mink file preview diff --git a/kDriveCore/Data/Cache/AccountManager.swift b/kDriveCore/Data/Cache/AccountManager.swift index 3f81233e7..047d4d4d9 100644 --- a/kDriveCore/Data/Cache/AccountManager.swift +++ b/kDriveCore/Data/Cache/AccountManager.swift @@ -78,8 +78,7 @@ public protocol AccountManageable: AnyObject { func getFirstAvailableDriveFileManager(for userId: Int) throws -> DriveFileManager /// Create on the fly an "in memory" DriveFileManager for a specific share - func getInMemoryDriveFileManager(for publicShareId: String) -> DriveFileManager - + func getInMemoryDriveFileManager(for publicShareId: String, driveId: Int, rootFileId: Int) -> DriveFileManager func getApiFetcher(for userId: Int, token: ApiToken) -> DriveApiFetcher func getTokenForUserId(_ id: Int) -> ApiToken? func didUpdateToken(newToken: ApiToken, oldToken: ApiToken) @@ -211,7 +210,7 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { } } - public func getInMemoryDriveFileManager(for publicShareId: String) -> DriveFileManager { + public func getInMemoryDriveFileManager(for publicShareId: String, driveId: Int, rootFileId: Int) -> DriveFileManager { if let inMemoryDriveFileManager = driveFileManagers[publicShareId] { return inMemoryDriveFileManager } @@ -233,7 +232,9 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { let forzenPublicShareDrive = publicShareDrive.freeze() let apiFetcher = DriveApiFetcher(token: someToken, delegate: SomeRefreshTokenDelegate()) - let context = DriveFileManagerContext.publicShare(shareId: publicShareId) + let context = DriveFileManagerContext.publicShare(shareId: publicShareId, + driveId: driveId, + rootFileId: rootFileId) return DriveFileManager(drive: forzenPublicShareDrive, apiFetcher: apiFetcher, context: context) } diff --git a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift index f733d2743..b42faa6da 100644 --- a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift +++ b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift @@ -30,7 +30,7 @@ public enum DriveFileManagerContext { case sharedWithMe /// Dedicated in memory dataset for a public share link - case publicShare(shareId: String) + case publicShare(shareId: String, driveId: Int, rootFileId: Int) func realmURL(driveId: Int, driveUserId: Int) -> URL? { switch self { diff --git a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift index c022ca1a7..07d161d00 100644 --- a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift +++ b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift @@ -99,6 +99,9 @@ public final class DriveFileManager { /// Fetch and write into DB with this object public let database: Transactionable + /// Context this object was initialized with + public let context: DriveFileManagerContext + /// Build a realm configuration for a specific Drive public static func configuration(context: DriveFileManagerContext, driveId: Int, driveUserId: Int) -> Realm.Configuration { let realmURL = context.realmURL(driveId: driveId, driveUserId: driveUserId) @@ -215,9 +218,50 @@ public final class DriveFileManager { ) } + public var isPublicShare: Bool { + switch context { + case .drive: + return false + case .fileProvider: + return false + case .sharedWithMe: + return false + case .publicShare(let shareId): + return true + } + } + + public var publicShareId: String? { + switch context { + case .publicShare(let shareId, _, _): + return shareId + default: + return nil + } + } + + public var publicDriveId: Int? { + switch context { + case .publicShare(_, let driveId, _): + return driveId + default: + return nil + } + } + + public var publicRootFileId: Int? { + switch context { + case .publicShare(_, _, let rootFileId): + return rootFileId + default: + return nil + } + } + init(drive: Drive, apiFetcher: DriveApiFetcher, context: DriveFileManagerContext = .drive) { self.drive = drive self.apiFetcher = apiFetcher + self.context = context realmConfiguration = Self.configuration(context: context, driveId: drive.id, driveUserId: drive.userId) let realmURL = context.realmURL(driveId: drive.id, driveUserId: drive.userId) diff --git a/kDriveCore/Data/Models/File+Image.swift b/kDriveCore/Data/Models/File+Image.swift index 87fab6c8e..5fa873adc 100644 --- a/kDriveCore/Data/Models/File+Image.swift +++ b/kDriveCore/Data/Models/File+Image.swift @@ -16,10 +16,42 @@ along with this program. If not, see . */ +import InfomaniakCore import Kingfisher import UIKit public extension File { + /// Get a Thumbnail for a file from a public share + @discardableResult + func getPublicShareThumbnail(publicShareId: String, + publicDriveId: Int, + publicFileId: Int, + completion: @escaping ((UIImage, Bool) -> Void)) -> Kingfisher.DownloadTask? { + guard supportedBy.contains(.thumbnail), + let currentDriveFileManager = accountManager.currentDriveFileManager else { + completion(icon, false) + return nil + } + + let thumbnailURL = Endpoint.shareLinkFileThumbnail(driveId: publicDriveId, + linkUuid: publicShareId, + fileId: publicFileId).url + + return KingfisherManager.shared.retrieveImage(with: thumbnailURL) { result in + if let image = try? result.get().image { + completion(image, true) + } else { + // The file can become invalidated while retrieving the icon online + completion( + self.isInvalidated ? ConvertedType.unknown.icon : self + .icon, + false + ) + } + } + } + + /// Get a Thumbnail for a file for the current DriveFileManager @discardableResult func getThumbnail(completion: @escaping ((UIImage, Bool) -> Void)) -> Kingfisher.DownloadTask? { if supportedBy.contains(.thumbnail), let currentDriveFileManager = accountManager.currentDriveFileManager { From 4e484608911c36c64713b7a1bb93fab8f4db58d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 18 Sep 2024 18:21:42 +0200 Subject: [PATCH 5/8] feat: Can navigate hierarchy of public share folders refactor: Simplified code --- .../Files/File List/FileListViewModel.swift | 5 ++- .../UI/Controller/Files/FilePresenter.swift | 6 ++++ .../Files/Preview/PreviewViewController.swift | 4 +-- .../View/Files/FileCollectionViewCell.swift | 22 ++++--------- .../Data/Api/PublicShareApiFetcher.swift | 6 ++-- kDriveCore/Data/Cache/AccountManager.swift | 5 ++- .../DriveFileManager+Transactionable.swift | 2 +- .../DriveFileManager/DriveFileManager.swift | 33 ++++--------------- kDriveCore/Data/Models/File.swift | 6 ++-- 9 files changed, 34 insertions(+), 55 deletions(-) diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift index 6201676ef..f43164e12 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift @@ -342,7 +342,10 @@ class FileListViewModel: SelectDelegate { } func didSelectFile(at indexPath: IndexPath) { - guard let file: File = getFile(at: indexPath) else { return } + guard let file: File = getFile(at: indexPath) else { + return + } + if ReachabilityListener.instance.currentStatus == .offline && !file.isDirectory && !file.isAvailableOffline { return } diff --git a/kDrive/UI/Controller/Files/FilePresenter.swift b/kDrive/UI/Controller/Files/FilePresenter.swift index 0d398e815..be01bcbeb 100644 --- a/kDrive/UI/Controller/Files/FilePresenter.swift +++ b/kDrive/UI/Controller/Files/FilePresenter.swift @@ -145,6 +145,12 @@ final class FilePresenter { let viewModel: FileListViewModel if driveFileManager.drive.sharedWithMe { viewModel = SharedWithMeViewModel(driveFileManager: driveFileManager, currentDirectory: file) + } else if let publicShareProxy = driveFileManager.publicShareProxy { + viewModel = PublicShareViewModel(publicShareProxy: publicShareProxy, + sortType: .nameAZ, + driveFileManager: driveFileManager, + currentDirectory: file, + apiFetcher: PublicShareApiFetcher()) } else if file.isTrashed || file.deletedAt != nil { viewModel = TrashListViewModel(driveFileManager: driveFileManager, currentDirectory: file) } else { diff --git a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift index 19c0c6477..ae80c6de3 100644 --- a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift +++ b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift @@ -609,8 +609,8 @@ final class PreviewViewController: UIViewController, PreviewContentCellDelegate, previewPageViewController.driveFileManager = driveFileManager previewPageViewController.normalFolderHierarchy = normalFolderHierarchy previewPageViewController.presentationOrigin = presentationOrigin - // currentIndex should be set at the end of the function as the it takes time and the viewDidLoad() is called before the - // function returns + // currentIndex should be set at the end of the function as the it takes time + // and the viewDidLoad() is called before the function returns // this should be fixed in the future with the refactor of the init previewPageViewController.currentIndex = IndexPath(row: index, section: 0) return previewPageViewController diff --git a/kDrive/UI/View/Files/FileCollectionViewCell.swift b/kDrive/UI/View/Files/FileCollectionViewCell.swift index e4172dd71..09e0b416b 100644 --- a/kDrive/UI/View/Files/FileCollectionViewCell.swift +++ b/kDrive/UI/View/Files/FileCollectionViewCell.swift @@ -49,14 +49,8 @@ protocol FileCellDelegate: AnyObject { var selectionMode: Bool var isSelected = false - /// UUID of the public share if file exists within a public share - let publicShareId: String? - - /// Drive ID of the public share if file exists within a public share - let publicDriveId: Int? - - /// Root file ID of the public share if file exists within a public share - let publicRootFileId: Int? + /// Public share data if file exists within a public share + let publicShareProxy: PublicShareProxy? private var downloadProgressObserver: ObservationToken? private var downloadObserver: ObservationToken? @@ -124,10 +118,7 @@ protocol FileCellDelegate: AnyObject { init(driveFileManager: DriveFileManager, file: File, selectionMode: Bool) { self.file = file self.selectionMode = selectionMode - publicShareId = driveFileManager.publicShareId - publicDriveId = driveFileManager.publicDriveId - publicRootFileId = driveFileManager.publicRootFileId - + publicShareProxy = driveFileManager.publicShareProxy categories = driveFileManager.drive.categories(for: file) } @@ -165,11 +156,10 @@ protocol FileCellDelegate: AnyObject { imageView.layer.masksToBounds = true imageView.backgroundColor = KDriveResourcesAsset.loaderDefaultColor.color - if let publicShareId = publicShareId, - let publicDriveId = publicDriveId { + if let publicShareProxy = publicShareProxy { // Fetch public share thumbnail - thumbnailDownloadTask = file.getPublicShareThumbnail(publicShareId: publicShareId, - publicDriveId: publicDriveId, + thumbnailDownloadTask = file.getPublicShareThumbnail(publicShareId: publicShareProxy.shareLinkUid, + publicDriveId: publicShareProxy.driveId, publicFileId: file.id) { [ requestFileId = file.id, weak self diff --git a/kDriveCore/Data/Api/PublicShareApiFetcher.swift b/kDriveCore/Data/Api/PublicShareApiFetcher.swift index 789de315e..341b22741 100644 --- a/kDriveCore/Data/Api/PublicShareApiFetcher.swift +++ b/kDriveCore/Data/Api/PublicShareApiFetcher.swift @@ -29,6 +29,7 @@ public class PublicShareApiFetcher: ApiFetcher { public func getMetadata(driveId: Int, shareLinkUid: String) async throws -> PublicShareMetadata { let shareLinkInfoUrl = Endpoint.shareLinkInfo(driveId: driveId, shareLinkUid: shareLinkUid).url + // TODO: Use authenticated token if availlable let request = Session.default.request(shareLinkInfoUrl) let metadata: PublicShareMetadata = try await perform(request: request) return metadata @@ -45,13 +46,14 @@ public class PublicShareApiFetcher: ApiFetcher { } /// Query a specific page - public func shareLinkFileChildren(publicShareProxy: PublicShareProxy, + public func shareLinkFileChildren(rootFolderId: Int, + publicShareProxy: PublicShareProxy, sortType: SortType, cursor: String? = nil) async throws -> ValidServerResponse<[File]> { let shareLinkFileChildren = Endpoint.shareLinkFileChildren( driveId: publicShareProxy.driveId, linkUuid: publicShareProxy.shareLinkUid, - fileId: publicShareProxy.fileId, + fileId: rootFolderId, sortType: sortType ) .cursored(cursor) diff --git a/kDriveCore/Data/Cache/AccountManager.swift b/kDriveCore/Data/Cache/AccountManager.swift index 047d4d4d9..53ee93823 100644 --- a/kDriveCore/Data/Cache/AccountManager.swift +++ b/kDriveCore/Data/Cache/AccountManager.swift @@ -232,9 +232,8 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { let forzenPublicShareDrive = publicShareDrive.freeze() let apiFetcher = DriveApiFetcher(token: someToken, delegate: SomeRefreshTokenDelegate()) - let context = DriveFileManagerContext.publicShare(shareId: publicShareId, - driveId: driveId, - rootFileId: rootFileId) + let publicShareProxy = PublicShareProxy(driveId: driveId, fileId: rootFileId, shareLinkUid: publicShareId) + let context = DriveFileManagerContext.publicShare(shareProxy: publicShareProxy) return DriveFileManager(drive: forzenPublicShareDrive, apiFetcher: apiFetcher, context: context) } diff --git a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift index b42faa6da..bf1be04df 100644 --- a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift +++ b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager+Transactionable.swift @@ -30,7 +30,7 @@ public enum DriveFileManagerContext { case sharedWithMe /// Dedicated in memory dataset for a public share link - case publicShare(shareId: String, driveId: Int, rootFileId: Int) + case publicShare(shareProxy: PublicShareProxy) func realmURL(driveId: Int, driveUserId: Int) -> URL? { switch self { diff --git a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift index 07d161d00..c924e5704 100644 --- a/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift +++ b/kDriveCore/Data/Cache/DriveFileManager/DriveFileManager.swift @@ -220,39 +220,17 @@ public final class DriveFileManager { public var isPublicShare: Bool { switch context { - case .drive: - return false - case .fileProvider: - return false - case .sharedWithMe: - return false - case .publicShare(let shareId): + case .publicShare: return true - } - } - - public var publicShareId: String? { - switch context { - case .publicShare(let shareId, _, _): - return shareId default: - return nil - } - } - - public var publicDriveId: Int? { - switch context { - case .publicShare(_, let driveId, _): - return driveId - default: - return nil + return false } } - public var publicRootFileId: Int? { + public var publicShareProxy: PublicShareProxy? { switch context { - case .publicShare(_, _, let rootFileId): - return rootFileId + case .publicShare(let shareProxy): + return shareProxy default: return nil } @@ -461,6 +439,7 @@ public final class DriveFileManager { try await files(in: rootProxy, fetchFiles: { let mySharedFiles = try await publicShareApiFetcher.shareLinkFileChildren( + rootFolderId: rootProxy.id, publicShareProxy: publicShareProxy, sortType: sortType ) diff --git a/kDriveCore/Data/Models/File.swift b/kDriveCore/Data/Models/File.swift index c3f24eea1..e4625c92d 100644 --- a/kDriveCore/Data/Models/File.swift +++ b/kDriveCore/Data/Models/File.swift @@ -184,9 +184,9 @@ public enum ConvertedType: String, CaseIterable { /// Minimal data needed to query a PublicShare public struct PublicShareProxy { - let driveId: Int - let fileId: Int - let shareLinkUid: String + public let driveId: Int + public let fileId: Int + public let shareLinkUid: String public init(driveId: Int, fileId: Int, shareLinkUid: String) { self.driveId = driveId From 3bdf94e0a2c650b61155ab769caabbf45c0967fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 19 Sep 2024 11:47:16 +0200 Subject: [PATCH 6/8] feat: Preview for public share --- .../Files/Preview/PreviewViewController.swift | 6 ++- ...DownloadingPreviewCollectionViewCell.swift | 20 ++++++++ kDriveCore/Data/Api/Endpoint+Share.swift | 2 +- kDriveCore/Data/Models/File+Image.swift | 49 +++++++++++++------ 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift index ae80c6de3..ef7b97cff 100644 --- a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift +++ b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift @@ -650,7 +650,11 @@ extension PreviewViewController: UICollectionViewDataSource { ) { let file = previewFiles[indexPath.row] if let cell = cell as? DownloadingPreviewCollectionViewCell { - cell.progressiveLoadingForFile(file) + if let publicShareProxy = driveFileManager.publicShareProxy { + cell.progressiveLoadingForPublicShareFile(file, publicShareProxy: publicShareProxy) + } else { + cell.progressiveLoadingForFile(file) + } } } diff --git a/kDrive/UI/View/Files/Preview/DownloadingPreviewCollectionViewCell.swift b/kDrive/UI/View/Files/Preview/DownloadingPreviewCollectionViewCell.swift index 18d434818..f26e027c0 100644 --- a/kDrive/UI/View/Files/Preview/DownloadingPreviewCollectionViewCell.swift +++ b/kDrive/UI/View/Files/Preview/DownloadingPreviewCollectionViewCell.swift @@ -97,6 +97,26 @@ class DownloadingPreviewCollectionViewCell: UICollectionViewCell, UIScrollViewDe return previewImageView } + func progressiveLoadingForPublicShareFile(_ file: File, publicShareProxy: PublicShareProxy) { + self.file = file + file.getPublicShareThumbnail(publicShareId: publicShareProxy.shareLinkUid, + publicDriveId: publicShareProxy.driveId, + publicFileId: file.id) { thumbnail, _ in + self.previewImageView.image = thumbnail + } + + previewDownloadTask = file.getPublicSharePreview(publicShareId: publicShareProxy.shareLinkUid, + publicDriveId: publicShareProxy.driveId, + publicFileId: file.id) { [weak previewImageView] preview in + guard let previewImageView else { + return + } + if let preview { + previewImageView.image = preview + } + } + } + func progressiveLoadingForFile(_ file: File) { self.file = file file.getThumbnail { thumbnail, _ in diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index dfb913047..2ea731f11 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -77,7 +77,7 @@ public extension Endpoint { /// Share mink file preview static func shareLinkFilePreview(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { - return shareLinkFile(driveId: driveId, linkUuid: linkUuid, fileId: fileId).appending(path: "/preview") + return shareLinkFileV2(driveId: driveId, linkUuid: linkUuid, fileId: fileId).appending(path: "/preview") } /// Download share link file diff --git a/kDriveCore/Data/Models/File+Image.swift b/kDriveCore/Data/Models/File+Image.swift index 5fa873adc..e7a07e627 100644 --- a/kDriveCore/Data/Models/File+Image.swift +++ b/kDriveCore/Data/Models/File+Image.swift @@ -27,8 +27,7 @@ public extension File { publicDriveId: Int, publicFileId: Int, completion: @escaping ((UIImage, Bool) -> Void)) -> Kingfisher.DownloadTask? { - guard supportedBy.contains(.thumbnail), - let currentDriveFileManager = accountManager.currentDriveFileManager else { + guard supportedBy.contains(.thumbnail) else { completion(icon, false) return nil } @@ -72,22 +71,40 @@ public extension File { } @discardableResult - func getPreview(completion: @escaping ((UIImage?) -> Void)) -> Kingfisher.DownloadTask? { - if let currentDriveFileManager = accountManager.currentDriveFileManager { - return KingfisherManager.shared.retrieveImage(with: imagePreviewUrl, - options: [ - .requestModifier(currentDriveFileManager.apiFetcher - .authenticatedKF), - .preloadAllAnimationData - ]) { result in - if let image = try? result.get().image { - completion(image) - } else { - completion(nil) - } + func getPublicSharePreview(publicShareId: String, + publicDriveId: Int, + publicFileId: Int, + completion: @escaping ((UIImage?) -> Void)) -> Kingfisher.DownloadTask? { + let previewURL = Endpoint.shareLinkFilePreview(driveId: publicDriveId, + linkUuid: publicShareId, + fileId: publicFileId).url + + return KingfisherManager.shared.retrieveImage(with: previewURL) { result in + if let image = try? result.get().image { + completion(image) + } else { + completion(nil) } - } else { + } + } + + @discardableResult + func getPreview(completion: @escaping ((UIImage?) -> Void)) -> Kingfisher.DownloadTask? { + guard let currentDriveFileManager = accountManager.currentDriveFileManager else { return nil } + + return KingfisherManager.shared.retrieveImage(with: imagePreviewUrl, + options: [ + .requestModifier(currentDriveFileManager.apiFetcher + .authenticatedKF), + .preloadAllAnimationData + ]) { result in + if let image = try? result.get().image { + completion(image) + } else { + completion(nil) + } + } } } From 245e8bf934c0d52e8877db98959dd4a4ba71aac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 23 Oct 2024 07:39:50 +0200 Subject: [PATCH 7/8] chore: PR Feedback --- kDrive/AppRouter.swift | 2 -- kDrive/UI/Controller/Files/File List/FileListViewModel.swift | 4 +--- kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift | 3 +-- kDrive/UI/View/Files/FileCollectionViewCell.swift | 2 +- kDriveCore/Data/Api/Endpoint+Share.swift | 1 - kDriveCore/Data/Cache/AccountManager.swift | 4 ++-- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/kDrive/AppRouter.swift b/kDrive/AppRouter.swift index 55a7c5e63..acfa150fc 100644 --- a/kDrive/AppRouter.swift +++ b/kDrive/AppRouter.swift @@ -621,8 +621,6 @@ public struct AppRouter: AppNavigable { currentDirectory: frozenRootFolder, apiFetcher: apiFetcher) let viewController = FileListViewController(viewModel: viewModel) - print("viewController:\(viewController) viewModel:\(viewModel) navigationController:\(navigationController)") - navigationController.pushViewController(viewController, animated: true) } } diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift index f43164e12..c8f9031a4 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift @@ -342,9 +342,7 @@ class FileListViewModel: SelectDelegate { } func didSelectFile(at indexPath: IndexPath) { - guard let file: File = getFile(at: indexPath) else { - return - } + guard let file: File = getFile(at: indexPath) else { return } if ReachabilityListener.instance.currentStatus == .offline && !file.isDirectory && !file.isAvailableOffline { return diff --git a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift index 0898ce34d..19997a86f 100644 --- a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift +++ b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift @@ -31,9 +31,8 @@ final class PublicShareViewModel: InMemoryFileListViewModel { fatalError("PublicShareViewModel requires a currentDirectory to work") } - // TODO: i18n let configuration = Configuration(selectAllSupported: false, - rootTitle: "public share", + rootTitle: KDriveCoreStrings.Localizable.sharedWithMeTitle, emptyViewType: .emptyFolder, supportsDrop: false, matomoViewPath: [MatomoUtils.Views.menu.displayName, "publicShare"]) diff --git a/kDrive/UI/View/Files/FileCollectionViewCell.swift b/kDrive/UI/View/Files/FileCollectionViewCell.swift index 09e0b416b..5b74cdc94 100644 --- a/kDrive/UI/View/Files/FileCollectionViewCell.swift +++ b/kDrive/UI/View/Files/FileCollectionViewCell.swift @@ -156,7 +156,7 @@ protocol FileCellDelegate: AnyObject { imageView.layer.masksToBounds = true imageView.backgroundColor = KDriveResourcesAsset.loaderDefaultColor.color - if let publicShareProxy = publicShareProxy { + if let publicShareProxy { // Fetch public share thumbnail thumbnailDownloadTask = file.getPublicShareThumbnail(publicShareId: publicShareProxy.shareLinkUid, publicDriveId: publicShareProxy.driveId, diff --git a/kDriveCore/Data/Api/Endpoint+Share.swift b/kDriveCore/Data/Api/Endpoint+Share.swift index 2ea731f11..511d4b15e 100644 --- a/kDriveCore/Data/Api/Endpoint+Share.swift +++ b/kDriveCore/Data/Api/Endpoint+Share.swift @@ -54,7 +54,6 @@ public extension Endpoint { shareUrlV3.appending(path: "/\(driveId)/share/\(linkUuid)/files/\(fileId)") } - /// Some legacy calls like thumbnails require a V2 call static func shareLinkFileV2(driveId: Int, linkUuid: String, fileId: Int) -> Endpoint { shareUrlV2.appending(path: "/\(driveId)/share/\(linkUuid)/files/\(fileId)") } diff --git a/kDriveCore/Data/Cache/AccountManager.swift b/kDriveCore/Data/Cache/AccountManager.swift index 251d676a3..9dee5130e 100644 --- a/kDriveCore/Data/Cache/AccountManager.swift +++ b/kDriveCore/Data/Cache/AccountManager.swift @@ -220,13 +220,13 @@ public class AccountManager: RefreshTokenDelegate, AccountManageable { } catch { fatalError("unable to update public share drive in base, \(error)") } - let forzenPublicShareDrive = publicShareDrive.freeze() + let frozenPublicShareDrive = publicShareDrive.freeze() let apiFetcher = DriveApiFetcher(token: someToken, delegate: SomeRefreshTokenDelegate()) let publicShareProxy = PublicShareProxy(driveId: driveId, fileId: rootFileId, shareLinkUid: publicShareId) let context = DriveFileManagerContext.publicShare(shareProxy: publicShareProxy) - return DriveFileManager(drive: forzenPublicShareDrive, apiFetcher: apiFetcher, context: context) + return DriveFileManager(drive: frozenPublicShareDrive, apiFetcher: apiFetcher, context: context) } public func getFirstAvailableDriveFileManager(for userId: Int) throws -> DriveFileManager { From fbf4989f0bcf5c8798cfa218082dedc4b5390c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 23 Oct 2024 08:00:54 +0200 Subject: [PATCH 8/8] chore: PR Feedback --- .../Files/File List/FileListViewModel.swift | 1 - .../Menu/Share/PublicShareViewModel.swift | 2 +- .../View/Files/FileCollectionViewCell.swift | 34 ++++++++----------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift index c8f9031a4..6201676ef 100644 --- a/kDrive/UI/Controller/Files/File List/FileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/FileListViewModel.swift @@ -343,7 +343,6 @@ class FileListViewModel: SelectDelegate { func didSelectFile(at indexPath: IndexPath) { guard let file: File = getFile(at: indexPath) else { return } - if ReachabilityListener.instance.currentStatus == .offline && !file.isDirectory && !file.isAvailableOffline { return } diff --git a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift index 19997a86f..3db8d8c5e 100644 --- a/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift +++ b/kDrive/UI/Controller/Menu/Share/PublicShareViewModel.swift @@ -30,7 +30,7 @@ final class PublicShareViewModel: InMemoryFileListViewModel { guard let currentDirectory else { fatalError("PublicShareViewModel requires a currentDirectory to work") } - + let configuration = Configuration(selectAllSupported: false, rootTitle: KDriveCoreStrings.Localizable.sharedWithMeTitle, emptyViewType: .emptyFolder, diff --git a/kDrive/UI/View/Files/FileCollectionViewCell.swift b/kDrive/UI/View/Files/FileCollectionViewCell.swift index 5b74cdc94..20294df74 100644 --- a/kDrive/UI/View/Files/FileCollectionViewCell.swift +++ b/kDrive/UI/View/Files/FileCollectionViewCell.swift @@ -164,35 +164,29 @@ protocol FileCellDelegate: AnyObject { requestFileId = file.id, weak self ] image, _ in - guard let self, - !self.file.isInvalidated, - !self.isSelected else { - return - } - - if file.id == requestFileId { - imageView.image = image - imageView.backgroundColor = nil - } + self?.setImage(image, on: imageView, requestFileId: requestFileId) } } else { // Fetch thumbnail thumbnailDownloadTask = file.getThumbnail { [requestFileId = file.id, weak self] image, _ in - guard let self, - !self.file.isInvalidated, - !self.isSelected else { - return - } - - if file.id == requestFileId { - imageView.image = image - imageView.backgroundColor = nil - } + self?.setImage(image, on: imageView, requestFileId: requestFileId) } } } + private func setImage(_ image: UIImage, on imageView: UIImageView, requestFileId: Int) { + guard !file.isInvalidated, + !isSelected else { + return + } + + if file.id == requestFileId { + imageView.image = image + imageView.backgroundColor = nil + } + } + deinit { downloadProgressObserver?.cancel() downloadObserver?.cancel()