#25 Support of sending images from Files and Camera
This commit is contained in:
parent
3ed8347304
commit
71278400a8
|
@ -149,6 +149,9 @@
|
|||
"compose.title.tryToUpload" = "Try to upload";
|
||||
"compose.title.delete" = "Delete";
|
||||
"compose.title.edit" = "Edit";
|
||||
"compose.title.photos" = "Photos library";
|
||||
"compose.title.camera" = "Take photo";
|
||||
"compose.title.files" = "Browse files";
|
||||
"compose.error.loadingPhotosFailed" = "Cannot retreive image from library.";
|
||||
"compose.error.postingPhotoFailed" = "Error during posting photo.";
|
||||
"compose.error.postingStatusFailed" = "Error during posting status.";
|
||||
|
|
|
@ -149,6 +149,9 @@
|
|||
"compose.title.tryToUpload" = "Saiatu igotzen";
|
||||
"compose.title.delete" = "Ezabatu";
|
||||
"compose.title.edit" = "Editatu";
|
||||
"compose.title.photos" = "Argazki-liburutegia";
|
||||
"compose.title.camera" = "Egin argazkia";
|
||||
"compose.title.files" = "Arakatu fitxategiak";
|
||||
"compose.error.loadingPhotosFailed" = "Ezin da liburutegiko irudia eskuratu.";
|
||||
"compose.error.postingPhotoFailed" = "Errorea argazkia argitaratzean.";
|
||||
"compose.error.postingStatusFailed" = "Errorea egoera argitaratzean.";
|
||||
|
|
|
@ -149,6 +149,9 @@
|
|||
"compose.title.tryToUpload" = "Essayer de télécharger";
|
||||
"compose.title.delete" = "Supprimer";
|
||||
"compose.title.edit" = "Editer";
|
||||
"compose.title.photos" = "Photos library";
|
||||
"compose.title.camera" = "Take photo";
|
||||
"compose.title.files" = "Browse files";
|
||||
"compose.error.loadingPhotosFailed" = "Impossible de récupérer l'image depuis la bibliothèque.";
|
||||
"compose.error.postingPhotoFailed" = "Erreur pendant le post de la photo.";
|
||||
"compose.error.postingStatusFailed" = "Erreur pendant le post du statut.";
|
||||
|
|
|
@ -149,6 +149,9 @@
|
|||
"compose.title.tryToUpload" = "Ponów";
|
||||
"compose.title.delete" = "Usuń";
|
||||
"compose.title.edit" = "Edytuj";
|
||||
"compose.title.photos" = "Biblioteka zdjęć";
|
||||
"compose.title.camera" = "Zrób zdjęcie";
|
||||
"compose.title.files" = "Przeglądaj pliki";
|
||||
"compose.error.loadingPhotosFailed" = "Nie można pobrać zdjęcia z biblioteki.";
|
||||
"compose.error.postingPhotoFailed" = "Błąd podczas publikowania zdjęcia.";
|
||||
"compose.error.postingStatusFailed" = "Błąd podczas wysyłania statusu.";
|
||||
|
|
|
@ -1437,6 +1437,7 @@
|
|||
INFOPLIST_FILE = Vernissage/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Vernissage;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Uploading photos to Pixelfed";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Saving photos from Pixelfed";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
|
@ -1478,6 +1479,7 @@
|
|||
INFOPLIST_FILE = Vernissage/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Vernissage;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Uploading photos to Pixelfed";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Saving photos from Pixelfed";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
|
|
|
@ -19,6 +19,9 @@ public class PhotoAttachment: ObservableObject, Identifiable, Equatable, Hashabl
|
|||
/// Information about image from share extension.
|
||||
public let nsItemProvider: NSItemProvider?
|
||||
|
||||
/// Information about image from camera sheet.
|
||||
public let uiImage: UIImage?
|
||||
|
||||
/// Variable used for presentation layer.
|
||||
@Published public var photoData: Data?
|
||||
|
||||
|
@ -34,11 +37,12 @@ public class PhotoAttachment: ObservableObject, Identifiable, Equatable, Hashabl
|
|||
/// Error from device.
|
||||
@Published public var loadError: Error?
|
||||
|
||||
public init(photosPickerItem: PhotosPickerItem? = nil, nsItemProvider: NSItemProvider? = nil) {
|
||||
public init(photosPickerItem: PhotosPickerItem? = nil, nsItemProvider: NSItemProvider? = nil, uiImage: UIImage? = nil) {
|
||||
self.id = UUID().uuidString
|
||||
|
||||
self.photosPickerItem = photosPickerItem
|
||||
self.nsItemProvider = nsItemProvider
|
||||
self.uiImage = uiImage
|
||||
}
|
||||
|
||||
public static func == (lhs: PhotoAttachment, rhs: PhotoAttachment) -> Bool {
|
||||
|
@ -54,6 +58,8 @@ public extension PhotoAttachment {
|
|||
|
||||
@MainActor
|
||||
func loadImage() async throws {
|
||||
|
||||
// Load images from Photos app.
|
||||
if let pickerItem = self.photosPickerItem,
|
||||
let transferable = try await pickerItem.createImageFileTranseferable() {
|
||||
self.photoUrl = transferable.url
|
||||
|
@ -62,6 +68,7 @@ public extension PhotoAttachment {
|
|||
return
|
||||
}
|
||||
|
||||
// Load images from share sheet (files app).
|
||||
if let itemProvider = self.nsItemProvider,
|
||||
let identifier = itemProvider.registeredTypeIdentifiers.first,
|
||||
let handledItemType = FileTypeSupported(rawValue: identifier),
|
||||
|
@ -71,6 +78,17 @@ public extension PhotoAttachment {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// Load images from camera.
|
||||
if let image = self.uiImage, let data = image.getJpegData() {
|
||||
let fileUrl = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).jpg")
|
||||
try data.write(to: fileUrl)
|
||||
|
||||
self.photoUrl = fileUrl
|
||||
self.photoData = data
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,10 +31,21 @@ public struct BaseComposeView: View {
|
|||
@State private var photosAreUploading = false
|
||||
@State private var photosPickerVisible = false
|
||||
|
||||
/// Images from camera pickler.
|
||||
@State private var images: [UIImage] = []
|
||||
|
||||
/// Images from share sheet or files application.
|
||||
@State private var attachments: [NSItemProvider]
|
||||
|
||||
/// Images from Photos app.
|
||||
@State private var selectedItems: [PhotosPickerItem] = []
|
||||
|
||||
/// Processed array with images.
|
||||
@State private var photosAttachment: [PhotoAttachment] = []
|
||||
|
||||
@State private var isCameraPickerPresented: Bool = false
|
||||
@State private var isFileImporterPresented: Bool = false
|
||||
|
||||
@State private var visibility = Pixelfed.Statuses.Visibility.pub
|
||||
@State private var visibilityText: LocalizedStringKey = "compose.title.everyone"
|
||||
@State private var visibilityImage = "globe.europe.africa"
|
||||
|
@ -144,6 +155,30 @@ public struct BaseComposeView: View {
|
|||
selection: $selectedItems,
|
||||
maxSelectionCount: 4,
|
||||
matching: .images)
|
||||
.fileImporter(isPresented: $isFileImporterPresented,
|
||||
allowedContentTypes: [.image],
|
||||
allowsMultipleSelection: true) { result in
|
||||
Task {
|
||||
if let urls = try? result.get() {
|
||||
await self.processFiles(urls: urls)
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $isCameraPickerPresented, content: {
|
||||
CameraPickerView(selectedImage: .init(
|
||||
get: { nil },
|
||||
set: { image in
|
||||
if let image {
|
||||
self.images.append(image)
|
||||
|
||||
Task {
|
||||
await self.loadPhotos()
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
.background(.black)
|
||||
})
|
||||
.interactiveDismissDisabled(self.interactiveDismissDisabled)
|
||||
}
|
||||
|
||||
|
@ -197,6 +232,10 @@ public struct BaseComposeView: View {
|
|||
item != photoAttachment.nsItemProvider
|
||||
})
|
||||
|
||||
self.images = self.images.filter({ item in
|
||||
item != photoAttachment.uiImage
|
||||
})
|
||||
|
||||
self.refreshScreenState()
|
||||
} upload: {
|
||||
Task {
|
||||
|
@ -399,10 +438,30 @@ public struct BaseComposeView: View {
|
|||
HStack {
|
||||
ScrollView(.horizontal) {
|
||||
HStack(alignment: .center, spacing: 20) {
|
||||
Button {
|
||||
hideKeyboard()
|
||||
self.focusedField = .unknown
|
||||
self.photosPickerVisible = true
|
||||
Menu {
|
||||
Button {
|
||||
hideKeyboard()
|
||||
self.focusedField = .unknown
|
||||
self.photosPickerVisible = true
|
||||
} label: {
|
||||
Label("compose.title.photos", systemImage: "photo")
|
||||
}
|
||||
|
||||
Button {
|
||||
hideKeyboard()
|
||||
self.focusedField = .unknown
|
||||
self.isCameraPickerPresented = true
|
||||
} label: {
|
||||
Label("compose.title.camera", systemImage: "camera")
|
||||
}
|
||||
|
||||
Button {
|
||||
hideKeyboard()
|
||||
self.focusedField = .unknown
|
||||
isFileImporterPresented = true
|
||||
} label: {
|
||||
Label("compose.title.files", systemImage: "folder")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: self.photosAreAttached ? "photo.fill.on.rectangle.fill" : "photo.on.rectangle")
|
||||
}
|
||||
|
@ -506,6 +565,14 @@ public struct BaseComposeView: View {
|
|||
return false
|
||||
}
|
||||
|
||||
private func processFiles(urls: [URL]) async {
|
||||
let items = urls.filter { $0.startAccessingSecurityScopedResource() }
|
||||
.compactMap { NSItemProvider(contentsOf: $0) }
|
||||
|
||||
self.attachments.append(contentsOf: items)
|
||||
await self.loadPhotos()
|
||||
}
|
||||
|
||||
private func loadPhotos() async {
|
||||
self.photosAreUploading = true
|
||||
self.publishDisabled = self.isPublishButtonDisabled()
|
||||
|
@ -534,6 +601,16 @@ public struct BaseComposeView: View {
|
|||
temporaryPhotosAttachment.append(PhotoAttachment(nsItemProvider: item))
|
||||
}
|
||||
|
||||
// Add to collection photos from camera picker.
|
||||
for item in self.images {
|
||||
if let photoAttachment = self.photosAttachment.first(where: { $0.uiImage == item }) {
|
||||
temporaryPhotosAttachment.append(photoAttachment)
|
||||
continue
|
||||
}
|
||||
|
||||
temporaryPhotosAttachment.append(PhotoAttachment(uiImage: item))
|
||||
}
|
||||
|
||||
// We can show new list on the screen.
|
||||
self.photosAttachment = temporaryPhotosAttachment
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
struct CameraPickerView: UIViewControllerRepresentable {
|
||||
@Environment(\.presentationMode) var isPresented
|
||||
@Binding var selectedImage: UIImage?
|
||||
|
||||
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
||||
let picker: CameraPickerView
|
||||
|
||||
init(picker: CameraPickerView) {
|
||||
self.picker = picker
|
||||
}
|
||||
|
||||
func imagePickerController(_ picker: UIImagePickerController,
|
||||
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
|
||||
guard let selectedImage = info[.originalImage] as? UIImage else {
|
||||
return
|
||||
}
|
||||
|
||||
self.picker.selectedImage = selectedImage
|
||||
self.picker.isPresented.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> UIImagePickerController {
|
||||
let imagePicker = UIImagePickerController()
|
||||
imagePicker.sourceType = .camera
|
||||
imagePicker.delegate = context.coordinator
|
||||
|
||||
return imagePicker
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(picker: self)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue