// // PhotoLibraryService.swift // Mastodon // // Created by MainasuK Cirno on 2021-4-29. // import os.log import UIKit import Combine import Photos import Alamofire import AlamofireImage import FLAnimatedImage final class PhotoLibraryService: NSObject { } extension PhotoLibraryService { enum PhotoLibraryError: Error { case noPermission case badPayload } enum ImageSource { case url(URL) case image(UIImage) } } extension PhotoLibraryService { func save(imageSource source: ImageSource) -> AnyPublisher { let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) let notificationFeedbackGenerator = UINotificationFeedbackGenerator() let imageDataPublisher: AnyPublisher = { switch source { case .url(let url): return PhotoLibraryService.fetchImageData(url: url) case .image(let image): return PhotoLibraryService.fetchImageData(image: image) } }() return imageDataPublisher .flatMap { data in PhotoLibraryService.save(imageData: data) } .handleEvents(receiveSubscription: { _ in impactFeedbackGenerator.impactOccurred() }, receiveCompletion: { completion in switch completion { case .failure: notificationFeedbackGenerator.notificationOccurred(.error) case .finished: notificationFeedbackGenerator.notificationOccurred(.success) } }) .eraseToAnyPublisher() } } extension PhotoLibraryService { func copy(imageSource source: ImageSource) -> AnyPublisher { let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) let notificationFeedbackGenerator = UINotificationFeedbackGenerator() let imageDataPublisher: AnyPublisher = { switch source { case .url(let url): return PhotoLibraryService.fetchImageData(url: url) case .image(let image): return PhotoLibraryService.fetchImageData(image: image) } }() return imageDataPublisher .flatMap { data in PhotoLibraryService.copy(imageData: data) } .handleEvents(receiveSubscription: { _ in impactFeedbackGenerator.impactOccurred() }, receiveCompletion: { completion in switch completion { case .failure: notificationFeedbackGenerator.notificationOccurred(.error) case .finished: notificationFeedbackGenerator.notificationOccurred(.success) } }) .eraseToAnyPublisher() } } extension PhotoLibraryService { static func fetchImageData(url: URL) -> AnyPublisher { AF.request(url).publishData() .tryMap { response in switch response.result { case .success(let data): return data case .failure(let error): throw error } } .eraseToAnyPublisher() } static func fetchImageData(image: UIImage) -> AnyPublisher { return Future { promise in DispatchQueue.global().async { let imageData = image.pngData() DispatchQueue.main.async { if let imageData = imageData { promise(.success(imageData)) } else { promise(.failure(PhotoLibraryError.badPayload)) } } } } .eraseToAnyPublisher() } static func save(imageData: Data) -> AnyPublisher { guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else { return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher() } return Future { promise in PHPhotoLibrary.shared().performChanges { PHAssetCreationRequest.forAsset().addResource(with: .photo, data: imageData, options: nil) } completionHandler: { isSuccess, error in if let error = error { promise(.failure(error)) } else { promise(.success(Void())) } } } .eraseToAnyPublisher() } static func copy(imageData: Data) -> AnyPublisher { Future { promise in DispatchQueue.global().async { let image = UIImage(data: imageData, scale: UIScreen.main.scale) DispatchQueue.main.async { if let image = image { UIPasteboard.general.image = image promise(.success(Void())) } else { promise(.failure(PhotoLibraryError.badPayload)) } } } } .eraseToAnyPublisher() } }