171 lines
5.2 KiB
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()
|
|
}
|
|
|
|
}
|