From f60c14cc7493a8f4d8c4f3e2c671690b34074ab6 Mon Sep 17 00:00:00 2001 From: fmodf Date: Wed, 3 Jul 2024 15:47:25 +0200 Subject: [PATCH] wip --- .../Attachments/AttachmentMediaManager.swift | 110 --------- .../AttachmentMediaPickerView.swift | 217 ++++-------------- .../View/UIToolkit/UIImage+Crop.swift | 44 ++++ 3 files changed, 95 insertions(+), 276 deletions(-) delete mode 100644 ConversationsClassic/View/Screens/Attachments/AttachmentMediaManager.swift create mode 100644 ConversationsClassic/View/UIToolkit/UIImage+Crop.swift diff --git a/ConversationsClassic/View/Screens/Attachments/AttachmentMediaManager.swift b/ConversationsClassic/View/Screens/Attachments/AttachmentMediaManager.swift deleted file mode 100644 index fa66f8c..0000000 --- a/ConversationsClassic/View/Screens/Attachments/AttachmentMediaManager.swift +++ /dev/null @@ -1,110 +0,0 @@ -// import AVFoundation -// import Photos -// import SwiftUI -// import UIKit -// -// class MediaManager: NSObject, ObservableObject { -// // @Published var photos: [UIImage] = [] -// @Published var cameraFeed: UIImage? -// -// // @Published var galleryAccessLevel: PHAuthorizationStatus = .notDetermined -// @Published var cameraAccessLevel: AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) -// -// override init() { -// super.init() -// NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) -// -// // DispatchQueue.main.async { [weak self] in -// // // self?.fetchPhotos() -// // } -// } -// -// // private func fetchPhotos() { -// // galleryAccessLevel = PHPhotoLibrary.authorizationStatus() -// // -// // let fetchOptions = PHFetchOptions() -// // fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] -// // let assets = PHAsset.fetchAssets(with: .image, options: fetchOptions) -// // -// // let manager = PHImageManager.default() -// // let option = PHImageRequestOptions() -// // option.isSynchronous = true -// // -// // assets.enumerateObjects { asset, _, _ in -// // manager.requestImage(for: asset, targetSize: CGSize(width: 200, height: 200), contentMode: .aspectFill, options: option) { image, _ in -// // if let image = image { -// // DispatchQueue.main.async { -// // self.photos.append(image) -// // } -// // } -// // } -// // } -// // } -// -// private func setupCameraFeed() { -// cameraAccessLevel = AVCaptureDevice.authorizationStatus(for: .video) -// -// let captureSession = AVCaptureSession() -// captureSession.sessionPreset = .medium -// -// guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { -// print("Unable to access the back camera!") -// return -// } -// -// do { -// let input = try AVCaptureDeviceInput(device: backCamera) -// if captureSession.canAddInput(input) { -// captureSession.addInput(input) -// } -// } catch { -// print("Error Unable to initialize back camera: \(error.localizedDescription)") -// } -// -// let videoOutput = AVCaptureVideoDataOutput() -// videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) -// if captureSession.canAddOutput(videoOutput) { -// captureSession.addOutput(videoOutput) -// } -// -// captureSession.startRunning() -// } -// } -// -// extension MediaManager: AVCaptureVideoDataOutputSampleBufferDelegate { -// func captureOutput(_: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from _: AVCaptureConnection) { -// print("Capturing output started") -// guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { -// return -// } -// -// let ciImage = CIImage(cvPixelBuffer: pixelBuffer) -// let context = CIContext() -// guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { -// return -// } -// -// DispatchQueue.main.async { -// self.cameraFeed = UIImage(cgImage: cgImage) -// print("Updated camera feed") -// } -// } -// } -// -// extension MediaManager { -// func openAppSettings() { -// if -// let appSettingsUrl = URL(string: UIApplication.openSettingsURLString), -// UIApplication.shared.canOpenURL(appSettingsUrl) -// { -// UIApplication.shared.open(appSettingsUrl, completionHandler: nil) -// } -// } -// -// @objc private func appDidBecomeActive() { -// // Update access levels -// // galleryAccessLevel = PHPhotoLibrary.authorizationStatus() -// cameraAccessLevel = AVCaptureDevice.authorizationStatus(for: .video) -// setupCameraFeed() -// } -// } diff --git a/ConversationsClassic/View/Screens/Attachments/AttachmentMediaPickerView.swift b/ConversationsClassic/View/Screens/Attachments/AttachmentMediaPickerView.swift index 0305e68..b591b6f 100644 --- a/ConversationsClassic/View/Screens/Attachments/AttachmentMediaPickerView.swift +++ b/ConversationsClassic/View/Screens/Attachments/AttachmentMediaPickerView.swift @@ -4,9 +4,9 @@ import SwiftUI struct AttachmentMediaPickerView: View { @State private var isCameraAccessGranted = AVCaptureDevice.authorizationStatus(for: .video) == .authorized - @State private var isGalleryAccessGranted = PHPhotoLibrary.authorizationStatus() == .authorized - @State private var images = [UIImage]() + + @State private var photos = [PhotoView]() let gridSize = UIScreen.main.bounds.width / 3 @@ -55,12 +55,8 @@ struct AttachmentMediaPickerView: View { // For pictures if isGalleryAccessGranted { - ForEach(images.indices, id: \.self) { index in - Image(uiImage: images[index]) - .resizable() - .aspectRatio(1, contentMode: .fit) - .frame(maxWidth: .infinity) - .clipped() + ForEach(photos) { photo in + photo } } else { Button { @@ -86,8 +82,10 @@ struct AttachmentMediaPickerView: View { } } .onAppear { - checkCameraAccess() - checkGalleryAccess() + DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) { + checkCameraAccess() + checkGalleryAccess() + } } } @@ -153,13 +151,13 @@ struct AttachmentMediaPickerView: View { contentMode: .aspectFill, options: option ) { image, _ in - if let image = image { - DispatchQueue.main.async { - if let img = scaleAndCropImage(image, toSize: CGSize(width: gridSize, height: gridSize)) { - self.images.append(img) + image?.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in + if let image { + DispatchQueue.main.async { + self.photos.append(PhotoView(image: image, gridSize: gridSize)) } } - } + }) } } } @@ -172,17 +170,45 @@ struct AttachmentMediaPickerView: View { UIApplication.shared.open(appSettingsUrl, completionHandler: nil) } } +} - func scaleAndCropImage(_ image: UIImage, toSize size: CGSize) -> UIImage? { - let imageView = UIImageView(frame: CGRect(origin: .zero, size: size)) - imageView.contentMode = .scaleAspectFill - imageView.image = image - UIGraphicsBeginImageContextWithOptions(size, false, 0.0) - guard let context = UIGraphicsGetCurrentContext() else { return nil } - imageView.layer.render(in: context) - let result = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return result +private struct PhotoView: Identifiable, View { + let id = UUID() + let gridSize: CGFloat + + @State private var image: UIImage + @State private var ready = false + + init(image: UIImage, gridSize: CGFloat) { + self.image = image + self.gridSize = gridSize + } + + var body: some View { + if ready { + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: gridSize, height: gridSize) + .clipped() + } else { + ZStack { + Rectangle() + .fill(Color.Main.backgroundLight) + .overlay { + ProgressView() + } + .frame(width: gridSize, height: gridSize) + } + .onAppear { + image.scaleAndCropImage(toExampleSize: CGSize(width: gridSize, height: gridSize), completion: { image in + if let image { + self.image = image + ready = true + } + }) + } + } } } @@ -215,147 +241,6 @@ struct CameraView: UIViewRepresentable { } func updateUIView(_ uiView: CameraUIView, context _: Context) { - // Update the previewLayer frame when the view updates uiView.previewLayer?.frame = uiView.bounds } } - -// struct AttachmentMediaPickerView: View { -// @StateObject private var mediaManager = MediaManager() -// -// var body: some View { -// ScrollView { -// LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 3)) { -// ForEach(elements) { element in -// element -// } -// } -// .padding(.horizontal, 8) -// } -// .padding(.vertical, 8) -// } -// -// private var elements: [GridElement] { -// print("Creating elements") -// var result: [GridElement] = [] -// -// // camera -// if let feed = mediaManager.cameraFeed, mediaManager.cameraAccessLevel == .authorized { -// result.append(GridElement(id: UUID(), type: .cameraFeed, content: feed) { -// print("Go to capture???") -// }) -// print("Added camera feed") -// } else if mediaManager.cameraAccessLevel == .restricted { -// result.append(GridElement(id: UUID(), type: .cameraRestricted, content: nil) { -// print("Show alert") -// }) -// print("Added camera restricted") -// } else { -// result.append(GridElement(id: UUID(), type: .cameraAskButton, content: nil) { -// mediaManager.openAppSettings() -// }) -// print("Added camera ask button") -// } -// -// // photos -// // if mediaManager.galleryAccessLevel == .restricted { -// // result.append(GridElement(id: UUID(), type: .photoRestricted, content: nil)) -// // } else { -// // for photo in mediaManager.photos { -// // result.append(GridElement(id: UUID(), type: .photo, content: photo)) -// // } -// // if mediaManager.galleryAccessLevel != .authorized { -// // result.append(GridElement(id: UUID(), type: .photoAskButton, content: nil)) -// // } -// // } -// -// return result -// } -// } -// -// private enum GridElementType { -// case cameraFeed -// case cameraAskButton -// case cameraRestricted -// case photo -// case photoAskButton -// case photoRestricted -// } -// -// private struct GridElement: View, Identifiable { -// let id: UUID -// let type: GridElementType -// let content: UIImage? -// let action: () -> Void -// -// var body: some View { -// switch type { -// case .cameraFeed: -// image -// .resizable() -// .aspectRatio(contentMode: .fill) -// .frame(width: 100, height: 100) -// .clipped() -// -// case .cameraAskButton: -// Button { -// action() -// } label: { -// RoundedRectangle(cornerRadius: 5) -// .stroke(Color.Main.backgroundDark, lineWidth: 2) -// .overlay { -// Image(systemName: "camera") -// .foregroundColor(.Material.tortoiseLight300) -// .font(.system(size: 40)) -// } -// .frame(height: 100) -// // .resizable() -// // .aspectRatio(contentMode: .fill) -// // .frame(width: 100, height: 100) -// // .clipped() -// } -// -// case .photo: -// image -// .resizable() -// .aspectRatio(contentMode: .fill) -// .frame(width: 100, height: 100) -// .clipped() -// -// case .photoAskButton: -// Button { -// action() -// } label: { -// Image(systemName: "photo.badge.plus") -// .resizable() -// .aspectRatio(contentMode: .fill) -// .frame(width: 100, height: 100) -// .clipped() -// } -// -// case .photoRestricted, .cameraRestricted: -// Button { -// action() -// } label: { -// Image(systemName: "cross") -// .resizable() -// .aspectRatio(contentMode: .fill) -// .frame(width: 100, height: 100) -// .clipped() -// } -// } -// } -// -// private var image: Image { -// guard let content = content else { -// return Image(systemName: "questionmark.square.dashed") -// } -// return Image(uiImage: content) -// } -// } -// -// struct AttachmentMediaPickerView_Previews: PreviewProvider { -// static var previews: some View { -// AttachmentMediaPickerView() -// } -// } diff --git a/ConversationsClassic/View/UIToolkit/UIImage+Crop.swift b/ConversationsClassic/View/UIToolkit/UIImage+Crop.swift new file mode 100644 index 0000000..4fd8ba1 --- /dev/null +++ b/ConversationsClassic/View/UIToolkit/UIImage+Crop.swift @@ -0,0 +1,44 @@ +import UIKit + +extension UIImage { + func scaleAndCropImage(toExampleSize _: CGSize, completion: @escaping (UIImage?) -> Void) { + DispatchQueue.global(qos: .background).async { + guard let cgImage = self.cgImage else { + DispatchQueue.main.async { + completion(nil) + } + return + } + let contextImage: UIImage = .init(cgImage: cgImage) + var contextSize: CGSize = contextImage.size + + var posX: CGFloat = 0.0 + var posY: CGFloat = 0.0 + let cgwidth: CGFloat = self.size.width + let cgheight: CGFloat = self.size.height + + // Check and handle if the image is wider than the requested size + if contextSize.width > contextSize.height { + posX = ((contextSize.width - contextSize.height) / 2) + contextSize.width = contextSize.height + } else if contextSize.width < contextSize.height { + // Check and handle if the image is taller than the requested size + posY = ((contextSize.height - contextSize.width) / 2) + contextSize.height = contextSize.width + } + + let rect: CGRect = .init(x: posX, y: posY, width: cgwidth, height: cgheight) + guard let contextCg = contextImage.cgImage, let imgRef = contextCg.cropping(to: rect) else { + DispatchQueue.main.async { + completion(nil) + } + return + } + let image: UIImage = .init(cgImage: imgRef, scale: self.scale, orientation: self.imageOrientation) + + DispatchQueue.main.async { + completion(image) + } + } + } +}