From 4baaf3368b65b2f3175e0ef92db005381f98cb50 Mon Sep 17 00:00:00 2001 From: CMK Date: Tue, 6 Jul 2021 20:02:30 +0800 Subject: [PATCH] feat: make copy action works in media preview scene --- .../StatusProvider+UITableViewDelegate.swift | 8 ++------ .../MediaPreviewViewController.swift | 19 +++++++++++++++++++ .../MediaPreviewImageViewController.swift | 10 ++++++++++ Mastodon/Service/PhotoLibraryService.swift | 18 ++++++++++++++---- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift index dae79d228..77580296b 100644 --- a/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift +++ b/Mastodon/Protocol/StatusProvider/StatusProvider+UITableViewDelegate.swift @@ -202,14 +202,10 @@ extension StatusTableViewCellDelegate where Self: StatusProvider { return self.context.photoLibraryService.copyImage(url: url) } .switchToLatest() - .sink(receiveCompletion: { [weak self] completion in - guard let self = self else { return } + .sink(receiveCompletion: { completion in switch completion { case .failure(let error): - guard let error = error as? PhotoLibraryService.PhotoLibraryError, - case .noPermission = error else { return } - let alertController = SettingService.openSettingsAlertController(title: L10n.Common.Alerts.SavePhotoFailure.title, message: L10n.Common.Alerts.SavePhotoFailure.message) - self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) case .finished: break } diff --git a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift index a0afe1ccd..0f2d06b9b 100644 --- a/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift +++ b/Mastodon/Scene/MediaPreview/MediaPreviewViewController.swift @@ -226,6 +226,24 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { case .local(let meta): context.photoLibraryService.save(image: meta.image, withNotificationFeedback: true) } + case .copyPhoto: + switch viewController.viewModel.item { + case .status(let meta): + context.photoLibraryService.copyImage(url: meta.url) + .sink { completion in + switch completion { + case .failure(let error): + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription) + case .finished: + break + } + } receiveValue: { _ in + // do nothing + } + .store(in: &context.disposeBag) + case .local(let meta): + context.photoLibraryService.copy(image: meta.image, withNotificationFeedback: true) + } case .share: let applicationActivities: [UIActivity] = [ SafariActivity(sceneCoordinator: self.coordinator) @@ -236,6 +254,7 @@ extension MediaPreviewViewController: MediaPreviewImageViewControllerDelegate { ) activityViewController.popoverPresentationController?.sourceView = viewController.previewImageView.imageView self.present(activityViewController, animated: true, completion: nil) + } } diff --git a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift index 1bc8fdb2a..960746b0f 100644 --- a/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift +++ b/Mastodon/Scene/MediaPreview/Paging/Image/MediaPreviewImageViewController.swift @@ -133,6 +133,14 @@ extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate { guard let self = self else { return } self.delegate?.mediaPreviewImageViewController(self, contextMenuActionPerform: .savePhoto) } + + let copyAction = UIAction( + title: L10n.Common.Controls.Actions.copyPhoto, image: UIImage(systemName: "doc.on.doc")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off + ) { [weak self] _ in + os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: copy photo", ((#file as NSString).lastPathComponent), #line, #function) + guard let self = self else { return } + self.delegate?.mediaPreviewImageViewController(self, contextMenuActionPerform: .copyPhoto) + } let shareAction = UIAction( title: L10n.Common.Controls.Actions.share, image: UIImage(systemName: "square.and.arrow.up")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off @@ -145,6 +153,7 @@ extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate { let actionProvider: UIContextMenuActionProvider = { elements -> UIMenu? in return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ saveAction, + copyAction, shareAction ]) } @@ -162,6 +171,7 @@ extension MediaPreviewImageViewController: UIContextMenuInteractionDelegate { extension MediaPreviewImageViewController { enum ContextMenuAction { case savePhoto + case copyPhoto case share } } diff --git a/Mastodon/Service/PhotoLibraryService.swift b/Mastodon/Service/PhotoLibraryService.swift index f6cb75482..45b47a836 100644 --- a/Mastodon/Service/PhotoLibraryService.swift +++ b/Mastodon/Service/PhotoLibraryService.swift @@ -26,6 +26,10 @@ extension PhotoLibraryService { extension PhotoLibraryService { func saveImage(url: URL) -> AnyPublisher { + guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else { + return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher() + } + return processImage(url: url) .handleEvents(receiveOutput: { image in self.save(image: image) @@ -45,10 +49,6 @@ extension PhotoLibraryService { let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) let notificationFeedbackGenerator = UINotificationFeedbackGenerator() - guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else { - return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher() - } - return ImagePipeline.shared.imagePublisher(with: url) .handleEvents(receiveSubscription: { _ in impactFeedbackGenerator.impactOccurred() @@ -87,6 +87,16 @@ extension PhotoLibraryService { notificationFeedbackGenerator.notificationOccurred(.success) } } + + func copy(image: UIImage, withNotificationFeedback: Bool = false) { + UIPasteboard.general.image = image + + // assert no error + if withNotificationFeedback { + let notificationFeedbackGenerator = UINotificationFeedbackGenerator() + notificationFeedbackGenerator.notificationOccurred(.success) + } + } @objc private func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { // TODO: notify banner