feat: add post video supports for document picker

This commit is contained in:
CMK 2021-05-31 18:03:31 +08:00
parent b9c262c84e
commit 6211663508
3 changed files with 129 additions and 56 deletions

View File

@ -95,7 +95,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
}()
private(set) lazy var documentPickerController: UIDocumentPickerViewController = {
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image])
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image, .movie])
documentPickerController.delegate = self
return documentPickerController
}()
@ -1102,20 +1102,13 @@ extension ComposeViewController: UIImagePickerControllerDelegate & UINavigationC
extension ComposeViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
do {
guard url.startAccessingSecurityScopedResource() else { return }
defer { url.stopAccessingSecurityScopedResource() }
let imageData = try Data(contentsOf: url)
let attachmentService = MastodonAttachmentService(
context: context,
imageData: imageData,
initalAuthenticationBox: viewModel.activeAuthenticationBox.value
)
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
} catch {
os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
}
let attachmentService = MastodonAttachmentService(
context: context,
documentURL: url,
initalAuthenticationBox: viewModel.activeAuthenticationBox.value
)
viewModel.attachmentServices.value = viewModel.attachmentServices.value + [attachmentService]
}
}

View File

@ -24,7 +24,7 @@ final class MastodonAttachmentService {
weak var delegate: MastodonAttachmentServiceDelegate?
let identifier = UUID()
// input
let context: AppContext
var authenticationBox: AuthenticationService.MastodonAuthenticationBox?
@ -85,6 +85,65 @@ final class MastodonAttachmentService {
self.uploadStateMachine.enter(UploadState.Initial.self)
}
.store(in: &disposeBag)
}
init(
context: AppContext,
image: UIImage,
initalAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
) {
self.context = context
self.authenticationBox = initalAuthenticationBox
// end init
setupServiceObserver()
file.value = .jpeg(image.jpegData(compressionQuality: 0.75))
uploadStateMachine.enter(UploadState.Initial.self)
}
init(
context: AppContext,
documentURL: URL,
initalAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
) {
self.context = context
self.authenticationBox = initalAuthenticationBox
// end init
setupServiceObserver()
Just(documentURL)
.flatMap { documentURL -> AnyPublisher<Mastodon.Query.MediaAttachment, Error> in
return MastodonAttachmentService.loadAttachment(url: documentURL)
}
.sink { [weak self] completion in
guard let self = self else { return }
switch completion {
case .failure(let error):
self.error.value = error
self.uploadStateMachine.enter(UploadState.Fail.self)
case .finished:
break
}
} receiveValue: { [weak self] file in
guard let self = self else { return }
self.file.value = file
self.uploadStateMachine.enter(UploadState.Initial.self)
}
.store(in: &disposeBag)
uploadStateMachine.enter(UploadState.Initial.self)
}
private func setupServiceObserver() {
uploadStateMachineSubject
.sink { [weak self] state in
guard let self = self else { return }
self.delegate?.mastodonAttachmentService(self, uploadStateDidChange: state)
}
.store(in: &disposeBag)
file
.map { file -> UIImage? in
@ -117,45 +176,6 @@ final class MastodonAttachmentService {
.store(in: &disposeBag)
}
init(
context: AppContext,
image: UIImage,
initalAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
) {
self.context = context
self.authenticationBox = initalAuthenticationBox
// end init
setupServiceObserver()
file.value = .jpeg(image.jpegData(compressionQuality: 0.75))
uploadStateMachine.enter(UploadState.Initial.self)
}
init(
context: AppContext,
imageData: Data,
initalAuthenticationBox: AuthenticationService.MastodonAuthenticationBox?
) {
self.context = context
self.authenticationBox = initalAuthenticationBox
// end init
setupServiceObserver()
self.file.value = .jpeg(imageData)
uploadStateMachine.enter(UploadState.Initial.self)
}
private func setupServiceObserver() {
uploadStateMachineSubject
.sink { [weak self] state in
guard let self = self else { return }
self.delegate?.mastodonAttachmentService(self, uploadStateDidChange: state)
}
.store(in: &disposeBag)
}
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
@ -189,3 +209,64 @@ extension MastodonAttachmentService: Equatable, Hashable {
}
}
extension MastodonAttachmentService {
private static func createWorkingQueue() -> DispatchQueue {
return DispatchQueue(label: "org.joinmastodon.Mastodon.MastodonAttachmentService.\(UUID().uuidString)")
}
static func loadAttachment(url: URL) -> AnyPublisher<Mastodon.Query.MediaAttachment, Error> {
guard let uti = UTType(filenameExtension: url.pathExtension) else {
return Fail(error: AttachmentError.invalidAttachmentType).eraseToAnyPublisher()
}
if uti.conforms(to: .image) {
return loadImageAttachment(url: url)
} else if uti.conforms(to: .movie) {
return loadVideoAttachment(url: url)
} else {
return Fail(error: AttachmentError.invalidAttachmentType).eraseToAnyPublisher()
}
}
static func loadImageAttachment(url: URL) -> AnyPublisher<Mastodon.Query.MediaAttachment, Error> {
Future<Mastodon.Query.MediaAttachment, Error> { promise in
createWorkingQueue().async {
do {
guard url.startAccessingSecurityScopedResource() else { return }
defer { url.stopAccessingSecurityScopedResource() }
let imageData = try Data(contentsOf: url)
promise(.success(.jpeg(imageData)))
} catch {
os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
promise(.failure(error))
}
}
}
.eraseToAnyPublisher()
}
static func loadVideoAttachment(url: URL) -> AnyPublisher<Mastodon.Query.MediaAttachment, Error> {
Future<Mastodon.Query.MediaAttachment, Error> { promise in
createWorkingQueue().async {
guard url.startAccessingSecurityScopedResource() else { return }
defer { url.stopAccessingSecurityScopedResource() }
let fileName = UUID().uuidString
let tempDirectoryURL = FileManager.default.temporaryDirectory
let fileURL = tempDirectoryURL.appendingPathComponent(fileName).appendingPathExtension(url.pathExtension)
do {
try FileManager.default.createDirectory(at: tempDirectoryURL, withIntermediateDirectories: true, attributes: nil)
try FileManager.default.copyItem(at: url, to: fileURL)
let file = Mastodon.Query.MediaAttachment.other(fileURL, fileExtension: fileURL.pathExtension, mimeType: UTType.movie.preferredMIMEType ?? "video/mp4")
promise(.success(file))
} catch {
promise(.failure(error))
}
}
}
.eraseToAnyPublisher()
}
}

View File

@ -95,7 +95,6 @@ enum PHPickerResultLoader {
} catch {
promise(.failure(error))
}
}
}
}