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>
This commit is contained in:
kernelkind 2024-02-19 15:53:55 -05:00 committed by William Casarin
parent 8d815fe4d6
commit 904ae6c24d
5 changed files with 139 additions and 88 deletions

View File

@ -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 */,

View File

@ -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>) {
}
}

View File

@ -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) {
}
}

View File

@ -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() {

View File

@ -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) {