damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 904ae6c24d8eef99d1ad367cd486fc8c23c4cd7d
parent 8d815fe4d6542960b85d21872a0cf554b074b1b9
Author: kernelkind <kernelkind@gmail.com>
Date:   Mon, 19 Feb 2024 15:53:55 -0500

picker: upgrade to newer image picker controller

Upgrade the ImagePicker to use PHPickerViewController instead of
UIImagePickerController for the photo library since the
PHPickerViewController allows for more options for how the user
shares their media, such as whether location data should be
included.

Create a CameraController since PHPickerViewController is only used for
the photo library. After capturing media with the camera, it will be
saved to the user's photo library and the photo library view will
appear for the user to select the media they captured. The user can then
toggle whether they would like apple to strip their media of location
data before handing it off to Damus.

Lightning-address: kernelkind@getalby.com
Signed-off-by: kernelkind <kernelkind@gmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Adamus/Views/Camera/CameraController.swift | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/ImagePicker.swift | 117++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mdamus/Views/PostView.swift | 23+++++------------------
Mdamus/Views/Profile/EditPictureControl.swift | 17++++++++---------
5 files changed, 139 insertions(+), 88 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -625,6 +625,7 @@ D7EDED342B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; }; D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; }; D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; }; + E02429952B7E97740088B16C /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02429942B7E97740088B16C /* CameraController.swift */; }; E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; }; E04A37C62B544F090029650D /* URIParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E04A37C52B544F090029650D /* URIParsing.swift */; }; E0E024112B7C19C20075735D /* TranslationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E024102B7C19C20075735D /* TranslationTests.swift */; }; @@ -1403,6 +1404,7 @@ D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; }; D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; }; D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = "<group>"; }; + E02429942B7E97740088B16C /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = "<group>"; }; E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; }; E04A37C52B544F090029650D /* URIParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIParsing.swift; sourceTree = "<group>"; }; E0E024102B7C19C20075735D /* TranslationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationTests.swift; sourceTree = "<group>"; }; @@ -2653,6 +2655,7 @@ isa = PBXGroup; children = ( BA3759962ABCCF360018D73B /* CameraPreview.swift */, + E02429942B7E97740088B16C /* CameraController.swift */, ); path = Camera; sourceTree = "<group>"; @@ -3143,6 +3146,7 @@ 4C7D09592A05BEAD00943473 /* KeyboardVisible.swift in Sources */, 4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */, 4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */, + E02429952B7E97740088B16C /* CameraController.swift in Sources */, 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */, D7CB5D3E2B116DAD00AD4105 /* NotificationsManager.swift in Sources */, 50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */, diff --git a/damus/Views/Camera/CameraController.swift b/damus/Views/Camera/CameraController.swift @@ -0,0 +1,66 @@ +// +// CameraController.swift +// damus +// +// Created by KernelKind on 2/15/24. +// + +import UIKit +import SwiftUI + +struct CameraController: UIViewControllerRepresentable { + + @Environment(\.presentationMode) + @Binding private var presentationMode + + let uploader: MediaUploader + let done: () -> Void + var imagesOnly: Bool = false + + final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { + let parent: CameraController + + init(_ parent: CameraController) { + self.parent = parent + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + if !parent.imagesOnly, let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL { + // Handle the selected video + UISaveVideoAtPathToSavedPhotosAlbum(videoURL.relativePath, nil, nil, nil) + } else if let cameraImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { + let orientedImage = cameraImage.fixOrientation() + UIImageWriteToSavedPhotosAlbum(orientedImage, nil, nil, nil) + } else if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { + let orientedImage = editedImage.fixOrientation() + UIImageWriteToSavedPhotosAlbum(orientedImage, nil, nil, nil) + } + + parent.done() + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + parent.presentationMode.dismiss() + } + } + + func makeCoordinator() -> Coordinator { + return Coordinator(self) + } + + func makeUIViewController(context: UIViewControllerRepresentableContext<CameraController>) -> UIImagePickerController { + let picker = UIImagePickerController() + picker.sourceType = .camera + picker.mediaTypes = ["public.image", "com.compuserve.gif"] + if uploader.supportsVideo && !imagesOnly { + picker.mediaTypes.append("public.movie") + } + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIImagePickerController, + context: UIViewControllerRepresentableContext<CameraController>) { + + } +} diff --git a/damus/Views/ImagePicker.swift b/damus/Views/ImagePicker.swift @@ -7,61 +7,52 @@ import UIKit import SwiftUI +import PhotosUI struct ImagePicker: UIViewControllerRepresentable { @Environment(\.presentationMode) - private var presentationMode + @Binding private var presentationMode - let uploader: MediaUploader - let sourceType: UIImagePickerController.SourceType - let pubkey: Pubkey @Binding var image_upload_confirm: Bool var imagesOnly: Bool = false - let onImagePicked: (URL) -> Void - let onVideoPicked: (URL) -> Void + let onMediaPicked: (MediaUpload) -> Void - final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { - @Binding private var presentationMode: PresentationMode - private let onImagePicked: (URL) -> Void - private let onVideoPicked: (URL) -> Void - @Binding var image_upload_confirm: Bool + final class Coordinator: NSObject, PHPickerViewControllerDelegate { + let parent: ImagePicker - init(presentationMode: Binding<PresentationMode>, - onImagePicked: @escaping (URL) -> Void, - onVideoPicked: @escaping (URL) -> Void, - image_upload_confirm: Binding<Bool>) { - _presentationMode = presentationMode - self.onImagePicked = onImagePicked - self.onVideoPicked = onVideoPicked - self._image_upload_confirm = image_upload_confirm + init(_ parent: ImagePicker) { + self.parent = parent } - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { - if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL { - // Handle the selected video - onVideoPicked(videoURL) - } else if let imageURL = info[UIImagePickerController.InfoKey.imageURL] as? URL { - // Handle the selected image - onImagePicked(imageURL) - } else if let cameraImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { - let orientedImage = cameraImage.fixOrientation() - if let imageURL = saveImageToTemporaryFolder(image: orientedImage, imageType: "jpeg") { - onImagePicked(imageURL) - } - } else if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { - let orientedImage = editedImage.fixOrientation() - if let editedImageURL = saveImageToTemporaryFolder(image: orientedImage, imageType: "jpeg") { - onImagePicked(editedImageURL) + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + if results.isEmpty { + self.parent.presentationMode.dismiss() + } + + for result in results { + if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { + result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in + guard let image = image as? UIImage, error == nil else { return } + let fixedImage = image.fixOrientation() + + if let savedURL = self.saveImageToTemporaryFolder(image: fixedImage) { + self.parent.onMediaPicked(.image(savedURL)) + self.parent.image_upload_confirm = true + } + } + } else if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { + result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { (url, error) in + guard let url, error == nil else { return } + + guard let url = self.saveVideoToTemporaryFolder(videoURL: url) else { return } + self.parent.onMediaPicked(.video(url)) + self.parent.image_upload_confirm = true + } } } - image_upload_confirm = true } - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - presentationMode.dismiss() - } - func saveImageToTemporaryFolder(image: UIImage, imageType: String = "png") -> URL? { // Convert UIImage to Data let imageData: Data? @@ -90,34 +81,38 @@ struct ImagePicker: UIViewControllerRepresentable { return nil } } + + func saveVideoToTemporaryFolder(videoURL: URL) -> URL? { + let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + let fileExtension = videoURL.pathExtension + let uniqueFileName = UUID().uuidString + (fileExtension.isEmpty ? "" : ".\(fileExtension)") + let destinationURL = temporaryDirectoryURL.appendingPathComponent(uniqueFileName) + + do { + try FileManager.default.copyItem(at: videoURL, to: destinationURL) + return destinationURL + } catch { + print("Error copying file: \(error.localizedDescription)") + return nil + } + } } func makeCoordinator() -> Coordinator { - return Coordinator(presentationMode: presentationMode, - onImagePicked: { url in - // Handle the selected image URL - onImagePicked(url) - }, - onVideoPicked: { videoURL in - // Handle the selected video URL - onVideoPicked(videoURL) - }, image_upload_confirm: $image_upload_confirm) - } - - func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController { - let picker = UIImagePickerController() - picker.sourceType = sourceType - picker.mediaTypes = ["public.image", "com.compuserve.gif"] - if uploader.supportsVideo && !imagesOnly { - picker.mediaTypes.append("public.movie") + Coordinator(self) } - picker.delegate = context.coordinator + + func makeUIViewController(context: Context) -> PHPickerViewController { + var configuration = PHPickerConfiguration(photoLibrary: .shared()) + configuration.selectionLimit = 1 + configuration.filter = imagesOnly ? .images : .any(of: [.images, .videos]) + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = context.coordinator as any PHPickerViewControllerDelegate return picker } - func updateUIViewController(_ uiViewController: UIImagePickerController, - context: UIViewControllerRepresentableContext<ImagePicker>) { - + func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { } } diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift @@ -420,10 +420,8 @@ struct PostView: View { } .background(DamusColors.adaptableWhite.edgesIgnoringSafeArea(.all)) .sheet(isPresented: $attach_media) { - ImagePicker(uploader: damus_state.settings.default_media_uploader, sourceType: .photoLibrary, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in - self.mediaToUpload = .image(img) - } onVideoPicked: { url in - self.mediaToUpload = .video(url) + ImagePicker(image_upload_confirm: $image_upload_confirm) { media in + self.mediaToUpload = media } .alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $image_upload_confirm) { Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) { @@ -436,20 +434,9 @@ struct PostView: View { } } .sheet(isPresented: $attach_camera) { - - ImagePicker(uploader: damus_state.settings.default_media_uploader, sourceType: .camera, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in - self.mediaToUpload = .image(img) - } onVideoPicked: { url in - self.mediaToUpload = .video(url) - } - .alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $image_upload_confirm) { - Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) { - if let mediaToUpload { - self.handle_upload(media: mediaToUpload) - self.attach_camera = false - } - } - Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {} + CameraController(uploader: damus_state.settings.default_media_uploader) { + self.attach_camera = false + self.attach_media = true } } .onAppear() { diff --git a/damus/Views/Profile/EditPictureControl.swift b/damus/Views/Profile/EditPictureControl.swift @@ -61,10 +61,8 @@ struct EditPictureControl: View { } .sheet(isPresented: $show_camera) { - ImagePicker(uploader: uploader, sourceType: .camera, pubkey: pubkey, image_upload_confirm: $image_upload_confirm, imagesOnly: true) { img in - self.mediaToUpload = .image(img) - } onVideoPicked: { url in - print("Cannot upload videos as profile image") + ImagePicker(image_upload_confirm: $image_upload_confirm, imagesOnly: true) { media in + self.mediaToUpload = media } .alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) { Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) { @@ -77,11 +75,12 @@ struct EditPictureControl: View { } } .sheet(isPresented: $show_library) { - ImagePicker(uploader: uploader, sourceType: .photoLibrary, pubkey: pubkey, image_upload_confirm: $image_upload_confirm, imagesOnly: true) { img in - self.mediaToUpload = .image(img) - - } onVideoPicked: { url in - print("Cannot upload videos as profile image") + ImagePicker(image_upload_confirm: $image_upload_confirm, imagesOnly: true) { media in + if case .image = media { + self.mediaToUpload = media + } else { + print("Cannot upload videos as profile image") + } } .alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) { Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {