diff --git a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift index 8afefc687..730428a81 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/UploadMediaAction.swift @@ -20,8 +20,116 @@ import UIKit import ownCloudSDK import ownCloudAppShared import Photos +import PhotosUI + +@available(iOS 14, *) +class PhotoPickerPresenter: NSObject, PHPickerViewControllerDelegate, PHPhotoLibraryChangeObserver { + + typealias AssetSelectionHandler = ([PHAsset]) -> Void + + var completionHandler: AssetSelectionHandler? + var parentViewController: UIViewController? + + private var assetIdentifiers = [String]() + + override init() { + super.init() + PHPhotoLibrary.shared().register(self) + } + + var pickerViewController: UIViewController { + var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) + configuration.preferredAssetRepresentationMode = .automatic + configuration.selectionLimit = 0 + let pickerViewController = PHPickerViewController(configuration: configuration) + pickerViewController.delegate = self + + return pickerViewController + } + + // MARK: - PHPickerViewControllerDelegate + + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + + picker.dismiss(animated: true) + + OnBackgroundQueue { + + // Get asset identifiers + for result in results { + if let identifier = result.assetIdentifier { + self.assetIdentifiers.append(identifier) + } + } + + // Fetch corresponding assets + let assets = self.attemptAssetsFetch() + + OnMainThread { + if results.count == assets.count{ + self.completionHandler?(assets) + } else { + self.presentLimitedLibraryPicker() + } + } + } + } + + private func presentLimitedLibraryPicker() { + guard let viewController = self.parentViewController else { return } + let library = PHPhotoLibrary.shared() + + let alert = ThemedAlertController(title: "Limited Photo Access".localized, message: "Access for the media selected for upload is limited".localized, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: "Change".localized, style: .default, handler: {_ in + library.presentLimitedLibraryPicker(from: viewController) + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in + self.assetIdentifiers.removeAll() + })) + + self.parentViewController?.present(alert, animated: true) + + } + + private func attemptAssetsFetch() -> [PHAsset] { + // Fetch corresponding assets + var assets = [PHAsset]() + + let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: assetIdentifiers, options: nil) + fetchResult.enumerateObjects({ (asset, _, _) in + assets.append(asset) + }) + + return assets + } + + func present(in viewController:UIViewController, with completion:@escaping AssetSelectionHandler) { + self.parentViewController = viewController + self.completionHandler = completion + viewController.present(self.pickerViewController, animated: true) + } + + // MARK: - PHPhotoLibraryChangeObserver + + func photoLibraryDidChange(_ changeInstance: PHChange) { + OnMainThread { + let assets = self.attemptAssetsFetch() + if assets.count > 0 { + self.completionHandler?(assets) + } else { + let alert = ThemedAlertController(title: "Limited Photo Access".localized, message: "No Access to the media selected for upload".localized, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: "OK".localized, style: .default, handler: nil)) + + self.parentViewController?.present(alert, animated: true) + } + } + } +} class UploadMediaAction: UploadBaseAction { + override class var identifier : OCExtensionIdentifier? { return OCExtensionIdentifier("com.owncloud.action.uploadphotos") } override class var category : ActionCategory? { return .normal } override class var name : String { return "Upload from your photo library".localized } @@ -29,6 +137,9 @@ class UploadMediaAction: UploadBaseAction { override class var keyCommand : String? { return "M" } override class var keyModifierFlags: UIKeyModifierFlags? { return [.command] } + // We need this to keep PhotoPickerPresenter alive in iOS14. Type is 'Any' since it is iOS14 only and you can't add @available() to stored properties + private var picker: Any? + private struct AssociatedKeys { static var actionKey = "action" } @@ -52,20 +163,33 @@ class UploadMediaAction: UploadBaseAction { } private func presentImageGalleryPicker() { - if let viewController = self.context.viewController { - let photoAlbumViewController = PhotoAlbumTableViewController() - photoAlbumViewController.selectionCallback = {(assets) in - self.completed() - guard let path = self.context.items.first?.path else { return } + func addAssetsToQueue(assets:[PHAsset]) { + guard let path = self.context.items.first?.path else { return } + guard let bookmark = self.core?.bookmark else { return } - guard let bookmark = self.core?.bookmark else { return } + MediaUploadQueue.shared.addUploads(assets, for: bookmark, at: path) + } - MediaUploadQueue.shared.addUploads(assets, for: bookmark, at: path) - } - let navigationController = ThemeNavigationController(rootViewController: photoAlbumViewController) + if let viewController = self.context.viewController { - viewController.present(navigationController, animated: true) + if #available(iOS 14, *) { + picker = PhotoPickerPresenter() + (picker as? PhotoPickerPresenter)?.present(in: viewController, with: { [weak self] (assets) in + self?.completed() + addAssetsToQueue(assets: assets) + self?.picker = nil + }) + } else { + let photoAlbumViewController = PhotoAlbumTableViewController() + photoAlbumViewController.selectionCallback = {(assets) in + self.completed() + addAssetsToQueue(assets: assets) + } + let navigationController = ThemeNavigationController(rootViewController: photoAlbumViewController) + + viewController.present(navigationController, animated: true) + } } else { self.completed(with: NSError(ocError: .internal)) } diff --git a/ownCloud/Resources/Info.plist b/ownCloud/Resources/Info.plist index b913def72..ae0143ed5 100644 --- a/ownCloud/Resources/Info.plist +++ b/ownCloud/Resources/Info.plist @@ -153,5 +153,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + PHPhotoLibraryPreventAutomaticLimitedAccessAlert + diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index 7927155c2..908b62837 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -836,3 +836,8 @@ "Prefer unedited photos" = "Prefer unedited photos"; "Prefer RAW photos" = "Prefer RAW photos"; "Prefer original videos" = "Prefer original videos"; + +/* Limited photo library access */ +"Limited Photo Access" = "Limited Photo Access"; +"Access for the media selected for upload is limited" = "Access for the media selected for upload is limited"; +"No Access to the media selected for upload" = "No Access to the media selected for upload"; diff --git a/ownCloudAppFramework/Licensing/Providers/App Store/OCLicenseAppStoreProvider.m b/ownCloudAppFramework/Licensing/Providers/App Store/OCLicenseAppStoreProvider.m index 178e8461f..e2eb9535a 100644 --- a/ownCloudAppFramework/Licensing/Providers/App Store/OCLicenseAppStoreProvider.m +++ b/ownCloudAppFramework/Licensing/Providers/App Store/OCLicenseAppStoreProvider.m @@ -68,7 +68,7 @@ - (instancetype)initWithItems:(NSArray *)items __weak OCLicenseAppStoreProvider *weakSelf = self; [OCIPNotificationCenter.sharedNotificationCenter addObserver:self forName:OCIPCNotificationNameLicenseAppStoreProviderDataChanged withHandler:^(OCIPNotificationCenter * _Nonnull notificationCenter, id _Nonnull observer, OCIPCNotificationName _Nonnull notificationName) { - OCWLogDebug(@"Received AppStoreProviderDataChanged notification"); + OCLogDebug(@"Received AppStoreProviderDataChanged notification"); [weakSelf loadReceipt]; }]; }