forked from zelo72/mastodon-ios
feat: add copy photo action for status
This commit is contained in:
parent
7a0ceb8fc4
commit
66de0593df
|
@ -70,15 +70,16 @@
|
|||
"cancel": "Cancel",
|
||||
"discard": "Discard",
|
||||
"try_again": "Try Again",
|
||||
"take_photo": "Take photo",
|
||||
"save_photo": "Save photo",
|
||||
"take_photo": "Take Photo",
|
||||
"save_photo": "Save Photo",
|
||||
"copy_photo": "Copy Photo",
|
||||
"sign_in": "Sign In",
|
||||
"sign_up": "Sign Up",
|
||||
"see_more": "See More",
|
||||
"preview": "Preview",
|
||||
"share": "Share",
|
||||
"share_user": "Share %s",
|
||||
"share_post": "Share post",
|
||||
"share_post": "Share Post",
|
||||
"open_in_safari": "Open in Safari",
|
||||
"find_people": "Find people to follow",
|
||||
"manually_search": "Manually search instead",
|
||||
|
|
|
@ -1005,7 +1005,8 @@ extension StatusSection {
|
|||
private static func setupStatusMoreButtonMenu(
|
||||
cell: StatusTableViewCell,
|
||||
dependency: NeedsDependency,
|
||||
status: Status) {
|
||||
status: Status
|
||||
) {
|
||||
|
||||
guard let userProvider = dependency as? UserProvider else { fatalError() }
|
||||
|
||||
|
|
|
@ -110,6 +110,8 @@ internal enum L10n {
|
|||
internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm")
|
||||
/// Continue
|
||||
internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue")
|
||||
/// Copy Photo
|
||||
internal static let copyPhoto = L10n.tr("Localizable", "Common.Controls.Actions.CopyPhoto")
|
||||
/// Delete
|
||||
internal static let delete = L10n.tr("Localizable", "Common.Controls.Actions.Delete")
|
||||
/// Discard
|
||||
|
@ -144,7 +146,7 @@ internal enum L10n {
|
|||
}
|
||||
/// Save
|
||||
internal static let save = L10n.tr("Localizable", "Common.Controls.Actions.Save")
|
||||
/// Save photo
|
||||
/// Save Photo
|
||||
internal static let savePhoto = L10n.tr("Localizable", "Common.Controls.Actions.SavePhoto")
|
||||
/// See More
|
||||
internal static let seeMore = L10n.tr("Localizable", "Common.Controls.Actions.SeeMore")
|
||||
|
@ -152,7 +154,7 @@ internal enum L10n {
|
|||
internal static let settings = L10n.tr("Localizable", "Common.Controls.Actions.Settings")
|
||||
/// Share
|
||||
internal static let share = L10n.tr("Localizable", "Common.Controls.Actions.Share")
|
||||
/// Share post
|
||||
/// Share Post
|
||||
internal static let sharePost = L10n.tr("Localizable", "Common.Controls.Actions.SharePost")
|
||||
/// Share %@
|
||||
internal static func shareUser(_ p1: Any) -> String {
|
||||
|
@ -164,7 +166,7 @@ internal enum L10n {
|
|||
internal static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp")
|
||||
/// Skip
|
||||
internal static let skip = L10n.tr("Localizable", "Common.Controls.Actions.Skip")
|
||||
/// Take photo
|
||||
/// Take Photo
|
||||
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
|
||||
/// Try Again
|
||||
internal static let tryAgain = L10n.tr("Localizable", "Common.Controls.Actions.TryAgain")
|
||||
|
|
|
@ -189,6 +189,35 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
})
|
||||
.store(in: &self.context.disposeBag)
|
||||
}
|
||||
let copyPhotoAction = 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.attachment(of: status, index: i)
|
||||
.setFailureType(to: Error.self)
|
||||
.compactMap { attachment -> AnyPublisher<UIImage, Error>? in
|
||||
guard let attachment = attachment, let url = URL(string: attachment.url) else { return nil }
|
||||
return self.context.photoLibraryService.copyImage(url: url)
|
||||
}
|
||||
.switchToLatest()
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
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))
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
}, receiveValue: { _ in
|
||||
// do nothing
|
||||
})
|
||||
.store(in: &self.context.disposeBag)
|
||||
}
|
||||
let shareAction = UIAction(
|
||||
title: L10n.Common.Controls.Actions.share, image: UIImage(systemName: "square.and.arrow.up")!, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off
|
||||
) { [weak self] _ in
|
||||
|
@ -210,7 +239,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
|||
})
|
||||
.store(in: &self.context.disposeBag)
|
||||
}
|
||||
let children = [savePhotoAction, shareAction]
|
||||
let children = [savePhotoAction, copyPhotoAction, shareAction]
|
||||
return UIMenu(title: "", image: nil, children: children)
|
||||
}
|
||||
contextMenuConfiguration.indexPath = indexPath
|
||||
|
|
|
@ -30,6 +30,7 @@ Please check your internet connection.";
|
|||
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||
"Common.Controls.Actions.Continue" = "Continue";
|
||||
"Common.Controls.Actions.CopyPhoto" = "Copy Photo";
|
||||
"Common.Controls.Actions.Delete" = "Delete";
|
||||
"Common.Controls.Actions.Discard" = "Discard";
|
||||
"Common.Controls.Actions.Done" = "Done";
|
||||
|
@ -46,16 +47,16 @@ Please check your internet connection.";
|
|||
"Common.Controls.Actions.Reply" = "Reply";
|
||||
"Common.Controls.Actions.ReportUser" = "Report %@";
|
||||
"Common.Controls.Actions.Save" = "Save";
|
||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
||||
"Common.Controls.Actions.SavePhoto" = "Save Photo";
|
||||
"Common.Controls.Actions.SeeMore" = "See More";
|
||||
"Common.Controls.Actions.Settings" = "Settings";
|
||||
"Common.Controls.Actions.Share" = "Share";
|
||||
"Common.Controls.Actions.SharePost" = "Share post";
|
||||
"Common.Controls.Actions.SharePost" = "Share Post";
|
||||
"Common.Controls.Actions.ShareUser" = "Share %@";
|
||||
"Common.Controls.Actions.SignIn" = "Sign In";
|
||||
"Common.Controls.Actions.SignUp" = "Sign Up";
|
||||
"Common.Controls.Actions.Skip" = "Skip";
|
||||
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
||||
"Common.Controls.Actions.TakePhoto" = "Take Photo";
|
||||
"Common.Controls.Actions.TryAgain" = "Try Again";
|
||||
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
|
||||
"Common.Controls.Friendship.Block" = "Block";
|
||||
|
|
|
@ -30,6 +30,7 @@ Please check your internet connection.";
|
|||
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||
"Common.Controls.Actions.Continue" = "Continue";
|
||||
"Common.Controls.Actions.CopyPhoto" = "Copy Photo";
|
||||
"Common.Controls.Actions.Delete" = "Delete";
|
||||
"Common.Controls.Actions.Discard" = "Discard";
|
||||
"Common.Controls.Actions.Done" = "Done";
|
||||
|
@ -46,16 +47,16 @@ Please check your internet connection.";
|
|||
"Common.Controls.Actions.Reply" = "Reply";
|
||||
"Common.Controls.Actions.ReportUser" = "Report %@";
|
||||
"Common.Controls.Actions.Save" = "Save";
|
||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
||||
"Common.Controls.Actions.SavePhoto" = "Save Photo";
|
||||
"Common.Controls.Actions.SeeMore" = "See More";
|
||||
"Common.Controls.Actions.Settings" = "Settings";
|
||||
"Common.Controls.Actions.Share" = "Share";
|
||||
"Common.Controls.Actions.SharePost" = "Share post";
|
||||
"Common.Controls.Actions.SharePost" = "Share Post";
|
||||
"Common.Controls.Actions.ShareUser" = "Share %@";
|
||||
"Common.Controls.Actions.SignIn" = "Sign In";
|
||||
"Common.Controls.Actions.SignUp" = "Sign Up";
|
||||
"Common.Controls.Actions.Skip" = "Skip";
|
||||
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
||||
"Common.Controls.Actions.TakePhoto" = "Take Photo";
|
||||
"Common.Controls.Actions.TryAgain" = "Try Again";
|
||||
"Common.Controls.Actions.UnblockDomain" = "Unblock %@";
|
||||
"Common.Controls.Friendship.Block" = "Block";
|
||||
|
|
|
@ -9,7 +9,7 @@ import os.log
|
|||
import UIKit
|
||||
import Combine
|
||||
import Photos
|
||||
import AlamofireImage
|
||||
import Nuke
|
||||
|
||||
final class PhotoLibraryService: NSObject {
|
||||
|
||||
|
@ -26,39 +26,51 @@ extension PhotoLibraryService {
|
|||
extension PhotoLibraryService {
|
||||
|
||||
func saveImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
||||
return processImage(url: url)
|
||||
.handleEvents(receiveOutput: { image in
|
||||
self.save(image: image)
|
||||
})
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func copyImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
||||
return processImage(url: url)
|
||||
.handleEvents(receiveOutput: { image in
|
||||
UIPasteboard.general.image = image
|
||||
})
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func processImage(url: URL) -> AnyPublisher<UIImage, Error> {
|
||||
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
||||
|
||||
return Future<UIImage, Error> { promise in
|
||||
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else {
|
||||
promise(.failure(PhotoLibraryError.noPermission))
|
||||
return
|
||||
}
|
||||
|
||||
ImageDownloader.default.download(URLRequest(url: url), completion: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
switch response.result {
|
||||
|
||||
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else {
|
||||
return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return ImagePipeline.shared.imagePublisher(with: url)
|
||||
.handleEvents(receiveSubscription: { _ in
|
||||
impactFeedbackGenerator.impactOccurred()
|
||||
}, receiveOutput: { response in
|
||||
self.save(image: response.image)
|
||||
}, receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s fail: %s", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription, error.localizedDescription)
|
||||
promise(.failure(error))
|
||||
case .success(let image):
|
||||
|
||||
notificationFeedbackGenerator.notificationOccurred(.error)
|
||||
case .finished:
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: download image %s success", ((#file as NSString).lastPathComponent), #line, #function, url.debugDescription)
|
||||
self.save(image: image)
|
||||
promise(.success(image))
|
||||
|
||||
notificationFeedbackGenerator.notificationOccurred(.success)
|
||||
}
|
||||
})
|
||||
}
|
||||
.handleEvents(receiveSubscription: { _ in
|
||||
impactFeedbackGenerator.impactOccurred()
|
||||
}, receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .failure:
|
||||
notificationFeedbackGenerator.notificationOccurred(.error)
|
||||
case .finished:
|
||||
notificationFeedbackGenerator.notificationOccurred(.success)
|
||||
.map { response in
|
||||
return response.image
|
||||
}
|
||||
})
|
||||
.eraseToAnyPublisher()
|
||||
.mapError { error in error as Error }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func save(image: UIImage, withNotificationFeedback: Bool = false) {
|
||||
|
|
Loading…
Reference in New Issue