From 7deae7f160fdb7438dc8929ccd7ba019b08a8537 Mon Sep 17 00:00:00 2001 From: Marcin Czachursk Date: Tue, 11 Apr 2023 07:37:05 +0200 Subject: [PATCH] Improve share extension --- Vernissage.xcodeproj/project.pbxproj | 12 +-- Vernissage/Views/ComposeView.swift | 7 +- VernissageShare/ComposeView.swift | 86 +++++++++---------- VernissageShare/Info.plist | 8 -- .../WidgetsKit/Models/FileTypeSupported.swift | 78 +++++++++++++++++ .../WidgetsKit/Models/PhotoAttachment.swift | 18 ++-- .../WidgetsKit/Models/TransferedFile.swift | 17 ++++ 7 files changed, 161 insertions(+), 65 deletions(-) create mode 100644 WidgetsKit/Sources/WidgetsKit/Models/FileTypeSupported.swift create mode 100644 WidgetsKit/Sources/WidgetsKit/Models/TransferedFile.swift diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 9ff84fc..5618406 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -1172,7 +1172,7 @@ CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 104; + CURRENT_PROJECT_VERSION = 105; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageWidget/Info.plist; @@ -1200,7 +1200,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 104; + CURRENT_PROJECT_VERSION = 105; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageWidget/Info.plist; @@ -1227,7 +1227,7 @@ CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 104; + CURRENT_PROJECT_VERSION = 105; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageShare/Info.plist; @@ -1254,7 +1254,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 104; + CURRENT_PROJECT_VERSION = 105; DEVELOPMENT_TEAM = B2U9FEKYP8; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = VernissageShare/Info.plist; @@ -1403,7 +1403,7 @@ CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 104; + CURRENT_PROJECT_VERSION = 105; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1443,7 +1443,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 104; + CURRENT_PROJECT_VERSION = 105; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; diff --git a/Vernissage/Views/ComposeView.swift b/Vernissage/Views/ComposeView.swift index b7d65c6..10ee470 100644 --- a/Vernissage/Views/ComposeView.swift +++ b/Vernissage/Views/ComposeView.swift @@ -530,6 +530,9 @@ struct ComposeView: View { self.photosAreUploading = false self.refreshScreenState() } catch { + self.photosAreUploading = false + self.refreshScreenState() + ErrorService.shared.handle(error, message: "compose.error.loadingPhotosFailed", showToastr: true) } } @@ -554,8 +557,8 @@ struct ComposeView: View { } // We are sending orginal file (not file compressed from memory). - guard let imageFileTranseferable = photoAttachment.imageFileTranseferable, - let data = try? Data(contentsOf: imageFileTranseferable.url), + guard let photoUrl = photoAttachment.photoUrl, + let data = try? Data(contentsOf: photoUrl), let uiImage = UIImage(data: data) else { return } diff --git a/VernissageShare/ComposeView.swift b/VernissageShare/ComposeView.swift index 1c0220d..9e85df0 100644 --- a/VernissageShare/ComposeView.swift +++ b/VernissageShare/ComposeView.swift @@ -469,54 +469,54 @@ struct ComposeView: View { } private func loadPhotos() async { - do { - self.photosAreUploading = true - self.publishDisabled = self.isPublishButtonDisabled() - self.interactiveDismissDisabled = self.isInteractiveDismissDisabled() + self.photosAreUploading = true + self.publishDisabled = self.isPublishButtonDisabled() + self.interactiveDismissDisabled = self.isInteractiveDismissDisabled() - // We have to create list with existing photos. - var temporaryPhotosAttachment: [PhotoAttachment] = [] + // We have to create list with existing photos. + var temporaryPhotosAttachment: [PhotoAttachment] = [] - // Add to collection photos selected on photo picker. - for item in self.selectedItems { - if let photoAttachment = self.photosAttachment.first(where: { $0.photosPickerItem == item }) { - temporaryPhotosAttachment.append(photoAttachment) - continue - } - - temporaryPhotosAttachment.append(PhotoAttachment(photosPickerItem: item)) + // Add to collection photos selected on photo picker. + for item in self.selectedItems { + if let photoAttachment = self.photosAttachment.first(where: { $0.photosPickerItem == item }) { + temporaryPhotosAttachment.append(photoAttachment) + continue } - // Add to collection photos from share sheet. - for item in self.attachments { - if let photoAttachment = self.photosAttachment.first(where: { $0.nsItemProvider == item }) { - temporaryPhotosAttachment.append(photoAttachment) - continue - } - - temporaryPhotosAttachment.append(PhotoAttachment(nsItemProvider: item)) - } - - // We can show new list on the screen. - self.photosAttachment = temporaryPhotosAttachment - - // Now we have to get from photos images as JPEG. - for item in self.photosAttachment.filter({ $0.photoData == nil }) { - try await item.loadImage() - } - - // Open again the keyboard. - self.focusedField = .content - - // Upload images which hasn't been uploaded yet. - await self.upload() - - // Change state of the screen. - self.photosAreUploading = false - self.refreshScreenState() - } catch { - ErrorService.shared.handle(error, message: "compose.error.loadingPhotosFailed", showToastr: true) + temporaryPhotosAttachment.append(PhotoAttachment(photosPickerItem: item)) } + + // Add to collection photos from share sheet. + for item in self.attachments { + if let photoAttachment = self.photosAttachment.first(where: { $0.nsItemProvider == item }) { + temporaryPhotosAttachment.append(photoAttachment) + continue + } + + temporaryPhotosAttachment.append(PhotoAttachment(nsItemProvider: item)) + } + + // We can show new list on the screen. + self.photosAttachment = temporaryPhotosAttachment + + // Now we have to get from photos images as JPEG. + for item in self.photosAttachment.filter({ $0.photoData == nil }) { + do { + try await item.loadImage() + } catch { + ErrorService.shared.handle(error, message: "Cannot load image from external library.") + } + } + + // Open again the keyboard. + self.focusedField = .content + + // Upload images which hasn't been uploaded yet. + await self.upload() + + // Change state of the screen. + self.photosAreUploading = false + self.refreshScreenState() } private func refreshScreenState() { diff --git a/VernissageShare/Info.plist b/VernissageShare/Info.plist index 28e9785..8a3a95e 100644 --- a/VernissageShare/Info.plist +++ b/VernissageShare/Info.plist @@ -10,14 +10,6 @@ NSExtensionActivationSupportsImageWithMaxCount 4 - NSExtensionActivationSupportsMovieWithMaxCount - 0 - NSExtensionActivationSupportsText - - NSExtensionActivationSupportsWebPageWithMaxCount - 0 - NSExtensionActivationSupportsWebURLWithMaxCount - 0 NSExtensionPointIdentifier diff --git a/WidgetsKit/Sources/WidgetsKit/Models/FileTypeSupported.swift b/WidgetsKit/Sources/WidgetsKit/Models/FileTypeSupported.swift new file mode 100644 index 0000000..486d146 --- /dev/null +++ b/WidgetsKit/Sources/WidgetsKit/Models/FileTypeSupported.swift @@ -0,0 +1,78 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import AVFoundation +import Foundation +import PhotosUI +import SwiftUI +import UIKit +import UniformTypeIdentifiers +import ServicesKit + +@MainActor +enum FileTypeSupported: String, CaseIterable { + case image = "public.image" + case jpeg = "public.jpeg" + case png = "public.png" + case tiff = "public.tiff" + case gif = "public.gif" + + case gif2 = "com.compuserve.gif" + case adobeRawImage = "com.adobe.raw-image" + case uiimage = "com.apple.uikit.image" + + public nonisolated static var allCases: [FileTypeSupported] { + [.image, .jpeg, .png, .tiff, .gif, .gif2, .uiimage, .adobeRawImage] + } + + func loadItemContent(item: NSItemProvider) async throws -> TransferedFile? { + let result = try await item.loadItem(forTypeIdentifier: rawValue) + + if isImageFile() { + if let image = result as? UIImage, + let data = image.getJpegData() { + let fileUrl = getTmpFileUrl() + try data.write(to: fileUrl) + + return TransferedFile(file: data, url: fileUrl) + } else if let imageURL = result as? URL, + let data = await ImageCompressService.shared.compressImageFrom(url: imageURL) { + let fileUrl = getTmpFileUrl() + try data.write(to: fileUrl) + + return TransferedFile(file: data, url: fileUrl) + } else if let data = result as? Data { + let fileUrl = getTmpFileUrl() + try data.write(to: fileUrl) + + return TransferedFile(file: data, url: fileUrl) + } + } + + if let transferable = try await item.createImageFileTranseferable(), + let data = await ImageCompressService.shared.compressImageFrom(url: transferable.url) { + return TransferedFile(file: data, url: transferable.url) + } + + if let image = result as? UIImage, + let data = image.getJpegData() { + let fileUrl = getTmpFileUrl() + try data.write(to: fileUrl) + + return TransferedFile(file: data, url: fileUrl) + } + + return nil + } + + func isImageFile() -> Bool { + return self == .jpeg || self == .png || self == .tiff || self == .image || self == .uiimage || self == .adobeRawImage + } + + func getTmpFileUrl() -> URL { + return URL.temporaryDirectory.appending(path: "\(UUID().uuidString).jpeg") + } +} diff --git a/WidgetsKit/Sources/WidgetsKit/Models/PhotoAttachment.swift b/WidgetsKit/Sources/WidgetsKit/Models/PhotoAttachment.swift index 6b09e2c..9ff640f 100644 --- a/WidgetsKit/Sources/WidgetsKit/Models/PhotoAttachment.swift +++ b/WidgetsKit/Sources/WidgetsKit/Models/PhotoAttachment.swift @@ -23,7 +23,7 @@ public class PhotoAttachment: ObservableObject, Identifiable, Equatable, Hashabl @Published public var photoData: Data? /// Property which stores orginal image file copied from Photos to tmp folder. - @Published public var imageFileTranseferable: ImageFileTranseferable? + @Published public var photoUrl: URL? /// Property stores information after upload to Pixelfed. @Published public var uploadedAttachment: UploadedAttachment? @@ -53,14 +53,20 @@ public extension PhotoAttachment { func loadImage() async throws { if let pickerItem = self.photosPickerItem, let transferable = try await pickerItem.createImageFileTranseferable() { - self.imageFileTranseferable = transferable + self.photoUrl = transferable.url self.photoData = await ImageCompressService.shared.compressImageFrom(url: transferable.url) + + return } if let itemProvider = self.nsItemProvider, - let transferable = try await itemProvider.createImageFileTranseferable() { - self.imageFileTranseferable = transferable - self.photoData = await ImageCompressService.shared.compressImageFrom(url: transferable.url) + let identifier = itemProvider.registeredTypeIdentifiers.first, + let handledItemType = FileTypeSupported(rawValue: identifier), + let transferredFile = try await handledItemType.loadItemContent(item: itemProvider) { + self.photoUrl = transferredFile.url + self.photoData = transferredFile.file + + return } } } @@ -86,7 +92,7 @@ public extension [PhotoAttachment] { func removeTmpFiles() { for file in self { - if let fileUrl = file.imageFileTranseferable?.url { + if let fileUrl = file.photoUrl { do { try FileManager.default.removeItem(at: fileUrl) } catch { diff --git a/WidgetsKit/Sources/WidgetsKit/Models/TransferedFile.swift b/WidgetsKit/Sources/WidgetsKit/Models/TransferedFile.swift new file mode 100644 index 0000000..71e106a --- /dev/null +++ b/WidgetsKit/Sources/WidgetsKit/Models/TransferedFile.swift @@ -0,0 +1,17 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +public struct TransferedFile { + public let file: Data + public let url: URL + + public init(file: Data, url: URL) { + self.file = file + self.url = url + } +}