diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index b74c35ed..da8f7455 100644 --- 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 = ""; }; D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = ""; }; D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = ""; }; + E02429942B7E97740088B16C /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = ""; }; E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = ""; }; E04A37C52B544F090029650D /* URIParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URIParsing.swift; sourceTree = ""; }; E0E024102B7C19C20075735D /* TranslationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationTests.swift; sourceTree = ""; }; @@ -2653,6 +2655,7 @@ isa = PBXGroup; children = ( BA3759962ABCCF360018D73B /* CameraPreview.swift */, + E02429942B7E97740088B16C /* CameraController.swift */, ); path = Camera; sourceTree = ""; @@ -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 new file mode 100644 index 00000000..6fc3f36c --- /dev/null +++ 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) -> 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) { + + } +} diff --git a/damus/Views/ImagePicker.swift b/damus/Views/ImagePicker.swift index d4c7b450..9a38ee43 100644 --- 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, - onImagePicked: @escaping (URL) -> Void, - onVideoPicked: @escaping (URL) -> Void, - image_upload_confirm: Binding) { - _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) -> 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) { - + func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { } } diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift index 8f6fd689..8b10b4fc 100644 --- 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 index 7abdc5fa..fdff34d2 100644 --- 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) {