mastodon-ios/Mastodon/Service/PhotoLibraryService.swift

171 lines
5.2 KiB
Swift

//
// 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<Void, Error> {
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
let imageDataPublisher: AnyPublisher<Data, Error> = {
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<Void, Error> {
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
let imageDataPublisher: AnyPublisher<Data, Error> = {
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<Data, Error> {
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<Data, Error> {
return Future<Data, Error> { 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<Void, Error> {
guard PHPhotoLibrary.authorizationStatus(for: .addOnly) != .denied else {
return Fail(error: PhotoLibraryError.noPermission).eraseToAnyPublisher()
}
return Future<Void, Error> { 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<Void, Error> {
Future<Void, Error> { 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()
}
}