2024-07-03 11:50:59 +00:00
|
|
|
import AVFoundation
|
|
|
|
import Photos
|
2024-07-02 09:56:27 +00:00
|
|
|
import SwiftUI
|
2024-07-02 10:23:27 +00:00
|
|
|
|
2024-07-02 09:56:27 +00:00
|
|
|
struct AttachmentMediaPickerView: View {
|
2024-07-03 11:50:59 +00:00
|
|
|
@State private var isCameraAccessGranted = AVCaptureDevice.authorizationStatus(for: .video) == .authorized
|
|
|
|
|
|
|
|
@State private var isGalleryAccessGranted = PHPhotoLibrary.authorizationStatus() == .authorized
|
|
|
|
@State private var images = [UIImage]()
|
|
|
|
|
|
|
|
let gridSize = UIScreen.main.bounds.width / 3
|
2024-07-02 09:56:27 +00:00
|
|
|
|
|
|
|
var body: some View {
|
2024-07-03 11:50:59 +00:00
|
|
|
let columns = Array(repeating: GridItem(.flexible(), spacing: 0), count: 3)
|
|
|
|
|
|
|
|
ScrollView(showsIndicators: false) {
|
|
|
|
LazyVGrid(columns: columns, spacing: 0) {
|
|
|
|
// For camera
|
|
|
|
if isCameraAccessGranted {
|
|
|
|
ZStack {
|
|
|
|
CameraView()
|
|
|
|
.aspectRatio(1, contentMode: .fit)
|
|
|
|
.frame(maxWidth: .infinity)
|
|
|
|
Image(systemName: "camera")
|
|
|
|
.resizable()
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
.frame(width: 40, height: 40)
|
|
|
|
.foregroundColor(.white)
|
|
|
|
.padding(8)
|
|
|
|
.background(Color.black.opacity(0.5))
|
|
|
|
.clipShape(Circle())
|
|
|
|
.padding(8)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Button {
|
|
|
|
openAppSettings()
|
|
|
|
} label: {
|
|
|
|
ZStack {
|
|
|
|
Rectangle()
|
|
|
|
.fill(Color.Main.backgroundLight)
|
|
|
|
.overlay {
|
|
|
|
VStack {
|
|
|
|
Image(systemName: "camera")
|
|
|
|
.foregroundColor(.Material.tortoiseLight300)
|
|
|
|
.font(.system(size: 30))
|
|
|
|
Text("Allow camera access")
|
|
|
|
.foregroundColor(.Main.black)
|
|
|
|
.font(.body3)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.frame(height: 100)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For pictures
|
|
|
|
if isGalleryAccessGranted {
|
|
|
|
ForEach(images.indices, id: \.self) { index in
|
|
|
|
Image(uiImage: images[index])
|
|
|
|
.resizable()
|
|
|
|
.aspectRatio(1, contentMode: .fit)
|
|
|
|
.frame(maxWidth: .infinity)
|
|
|
|
.clipped()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Button {
|
|
|
|
openAppSettings()
|
|
|
|
} label: {
|
|
|
|
ZStack {
|
|
|
|
Rectangle()
|
|
|
|
.fill(Color.Main.backgroundLight)
|
|
|
|
.overlay {
|
|
|
|
VStack {
|
|
|
|
Image(systemName: "photo")
|
|
|
|
.foregroundColor(.Material.tortoiseLight300)
|
|
|
|
.font(.system(size: 30))
|
|
|
|
Text("Allow gallery access")
|
|
|
|
.foregroundColor(.Main.black)
|
|
|
|
.font(.body3)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.frame(height: 100)
|
|
|
|
}
|
|
|
|
}
|
2024-07-02 10:23:27 +00:00
|
|
|
}
|
2024-07-02 09:56:27 +00:00
|
|
|
}
|
2024-07-03 10:47:59 +00:00
|
|
|
}
|
2024-07-03 11:50:59 +00:00
|
|
|
.onAppear {
|
|
|
|
checkCameraAccess()
|
|
|
|
checkGalleryAccess()
|
|
|
|
}
|
2024-07-03 10:47:59 +00:00
|
|
|
}
|
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
private func checkCameraAccess() {
|
|
|
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
|
|
|
switch status {
|
|
|
|
case .authorized:
|
|
|
|
isCameraAccessGranted = true
|
2024-07-03 10:47:59 +00:00
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
case .notDetermined:
|
|
|
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.isCameraAccessGranted = granted
|
|
|
|
}
|
|
|
|
}
|
2024-07-03 10:47:59 +00:00
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
case .denied, .restricted:
|
|
|
|
isCameraAccessGranted = false
|
2024-07-03 10:47:59 +00:00
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
@unknown default:
|
|
|
|
isCameraAccessGranted = false
|
|
|
|
}
|
|
|
|
}
|
2024-07-03 10:47:59 +00:00
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
private func checkGalleryAccess() {
|
|
|
|
let status = PHPhotoLibrary.authorizationStatus()
|
|
|
|
switch status {
|
|
|
|
case .authorized, .limited:
|
|
|
|
isGalleryAccessGranted = true
|
|
|
|
fetchImages()
|
2024-07-03 10:47:59 +00:00
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
case .notDetermined:
|
|
|
|
PHPhotoLibrary.requestAuthorization { status in
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.isGalleryAccessGranted = status == .authorized
|
|
|
|
if self.isGalleryAccessGranted {
|
|
|
|
self.fetchImages()
|
2024-07-03 10:47:59 +00:00
|
|
|
}
|
2024-07-03 11:50:59 +00:00
|
|
|
}
|
2024-07-03 10:47:59 +00:00
|
|
|
}
|
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
case .denied, .restricted:
|
|
|
|
isGalleryAccessGranted = false
|
|
|
|
|
|
|
|
@unknown default:
|
|
|
|
isGalleryAccessGranted = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func fetchImages() {
|
|
|
|
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
|
2024-07-03 10:47:59 +00:00
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
assets.enumerateObjects { asset, _, _ in
|
|
|
|
manager.requestImage(
|
|
|
|
for: asset,
|
|
|
|
targetSize: PHImageManagerMaximumSize,
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-03 10:47:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
func openAppSettings() {
|
|
|
|
if
|
|
|
|
let appSettingsUrl = URL(string: UIApplication.openSettingsURLString),
|
|
|
|
UIApplication.shared.canOpenURL(appSettingsUrl)
|
|
|
|
{
|
|
|
|
UIApplication.shared.open(appSettingsUrl, completionHandler: nil)
|
2024-07-02 09:56:27 +00:00
|
|
|
}
|
2024-07-03 11:50:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2024-07-02 09:56:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:50:59 +00:00
|
|
|
class CameraUIView: UIView {
|
|
|
|
var previewLayer: AVCaptureVideoPreviewLayer?
|
|
|
|
|
|
|
|
override func layoutSubviews() {
|
|
|
|
super.layoutSubviews()
|
|
|
|
previewLayer?.frame = bounds
|
2024-07-02 09:56:27 +00:00
|
|
|
}
|
|
|
|
}
|
2024-07-03 11:50:59 +00:00
|
|
|
|
|
|
|
struct CameraView: UIViewRepresentable {
|
|
|
|
func makeUIView(context _: Context) -> CameraUIView {
|
|
|
|
let view = CameraUIView()
|
|
|
|
|
|
|
|
let captureSession = AVCaptureSession()
|
|
|
|
guard let captureDevice = AVCaptureDevice.default(for: .video) else { return view }
|
|
|
|
guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { return view }
|
|
|
|
captureSession.addInput(input)
|
|
|
|
|
|
|
|
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
|
|
|
previewLayer.videoGravity = .resizeAspectFill
|
|
|
|
view.layer.addSublayer(previewLayer)
|
|
|
|
view.previewLayer = previewLayer
|
|
|
|
|
|
|
|
captureSession.startRunning()
|
|
|
|
|
|
|
|
return view
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
// }
|
|
|
|
// }
|