conversations-classic-ios/ConversationsClassic/View/Screens/Attachments/AttachmentMediaPickerView.swift

286 lines
11 KiB
Swift
Raw Normal View History

2024-07-03 11:50:59 +00:00
import AVFoundation
2024-07-04 10:40:27 +00:00
import MobileCoreServices
2024-07-03 11:50:59 +00:00
import Photos
2024-07-02 09:56:27 +00:00
import SwiftUI
2024-07-02 10:23:27 +00:00
2024-07-04 09:17:05 +00:00
struct SelectedMedia {
2024-07-04 09:53:45 +00:00
let id: String
2024-07-04 09:17:05 +00:00
}
2024-07-02 09:56:27 +00:00
struct AttachmentMediaPickerView: View {
2024-07-10 17:49:36 +00:00
@EnvironmentObject var store: AppStore
2024-07-03 13:47:25 +00:00
2024-07-04 09:17:05 +00:00
@State private var selectedMedia = [SelectedMedia]()
2024-07-04 10:40:27 +00:00
@State private var showCameraPicker = false
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)
2024-07-04 09:53:45 +00:00
VStack(spacing: 0) {
// List of media
ScrollView(showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 0) {
// For camera
2024-07-10 17:49:36 +00:00
if store.state.sharingState.isCameraAccessGranted {
2024-07-03 11:50:59 +00:00
ZStack {
2024-07-04 09:53:45 +00:00
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)
}
2024-07-04 10:40:27 +00:00
.onTapGesture {
showCameraPicker = true
}
2024-07-04 09:53:45 +00:00
} else {
Button {
openAppSettings()
} label: {
ZStack {
Rectangle()
.fill(Color.Material.Background.light)
.overlay {
VStack {
Image(systemName: "camera")
.foregroundColor(.Material.Elements.active)
.font(.system(size: 30))
Text("Allow camera access")
.foregroundColor(.Material.Text.main)
.font(.body3)
}
2024-07-03 11:50:59 +00:00
}
2024-07-04 09:53:45 +00:00
.frame(height: 100)
}
2024-07-03 11:50:59 +00:00
}
}
2024-07-04 09:53:45 +00:00
// For gallery
2024-07-10 17:49:36 +00:00
if store.state.sharingState.isGalleryAccessGranted {
ForEach(store.state.sharingState.galleryItems) { item in
GridViewItem(item: item)
2024-07-04 09:53:45 +00:00
}
} else {
Button {
openAppSettings()
} label: {
ZStack {
Rectangle()
.fill(Color.Material.Background.light)
.overlay {
VStack {
Image(systemName: "photo")
.foregroundColor(.Material.Elements.active)
.font(.system(size: 30))
Text("Allow gallery access")
.foregroundColor(.Material.Text.main)
.font(.body3)
}
2024-07-03 11:50:59 +00:00
}
2024-07-04 09:53:45 +00:00
.frame(height: 100)
}
2024-07-03 11:50:59 +00:00
}
}
2024-07-02 10:23:27 +00:00
}
2024-07-02 09:56:27 +00:00
}
2024-07-04 10:40:27 +00:00
.fullScreenCover(isPresented: $showCameraPicker) {
2024-07-10 17:49:36 +00:00
CameraPicker(sourceType: .camera) { data, type in
store.dispatch(.sharingAction(.cameraCaptured(media: data, type: type)))
showCameraPicker = false
store.dispatch(.sharingAction(.showSharing(false)))
}
.edgesIgnoringSafeArea(.all)
2024-07-04 10:40:27 +00:00
}
2024-07-04 09:53:45 +00:00
// Send panel
Rectangle()
.foregroundColor(.Material.Shape.black)
.frame(maxWidth: .infinity)
.frame(height: self.selectedMedia.isEmpty ? 0 : 50)
.overlay {
HStack {
Text(L10n.Attachment.Send.media)
.foregroundColor(.Material.Text.white)
.font(.body1)
Image(systemName: "arrow.up.circle")
.foregroundColor(.Material.Text.white)
.font(.body1)
.padding(.leading, 8)
}
.padding()
}
.clipped()
.onTapGesture {
2024-07-09 13:09:34 +00:00
let ids = selectedMedia.map { $0.id }
2024-07-10 17:49:36 +00:00
// sendGalleryMedia(ids: ids)
2024-07-10 14:13:47 +00:00
store.dispatch(.sharingAction(.showSharing(false)))
2024-07-04 09:53:45 +00:00
}
2024-07-03 10:47:59 +00:00
}
2024-07-03 11:50:59 +00:00
.onAppear {
2024-07-10 17:49:36 +00:00
store.dispatch(.sharingAction(.checkCameraAccess))
store.dispatch(.sharingAction(.checkGalleryAccess))
// DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) {
// checkCameraAccess()
// checkGalleryAccess()
// }
2024-07-03 11:50:59 +00:00
}
2024-07-10 17:49:36 +00:00
.onChange(of: store.state.sharingState.isGalleryAccessGranted) { granted in
if granted {
store.dispatch(.sharingAction(.fetchGallery))
2024-07-03 11:50:59 +00:00
}
}
}
2024-07-03 13:47:25 +00:00
}
2024-07-10 17:49:36 +00:00
private struct GridViewItem: View {
let item: SharingGalleryItem
2024-07-03 13:47:25 +00:00
var body: some View {
2024-07-10 17:49:36 +00:00
if let data = item.thumbnail {
2024-07-04 08:47:08 +00:00
ZStack {
2024-07-10 17:49:36 +00:00
Image(uiImage: UIImage(data: data) ?? UIImage())
2024-07-04 08:47:08 +00:00
.resizable()
.aspectRatio(contentMode: .fill)
2024-07-10 17:49:36 +00:00
.frame(width: Const.galleryGridSize, height: Const.galleryGridSize)
2024-07-04 08:47:08 +00:00
.clipped()
2024-07-10 17:49:36 +00:00
if let duration = item.duration {
2024-07-04 08:47:08 +00:00
VStack {
Spacer()
HStack {
Spacer()
Text(duration)
.foregroundColor(.Material.Text.white)
.font(.sub1)
.shadow(color: .black, radius: 2)
.padding(4)
}
}
}
2024-07-10 17:49:36 +00:00
// if selected {
// VStack {
// HStack {
// Spacer()
// Circle()
// .frame(width: 30, height: 30)
// .shadow(color: .black, radius: 2)
// .foregroundColor(.Material.Shape.white)
// .overlay {
// Image(systemName: "checkmark")
// .foregroundColor(.Material.Elements.active)
// .font(.body3)
// }
// .padding(4)
// }
// Spacer()
// }
// }
2024-07-04 09:17:05 +00:00
}
.onTapGesture {
2024-07-10 17:49:36 +00:00
// withAnimation {
// selected.toggle()
// if selected {
// selectedMedia.append(SelectedMedia(id: id))
// } else {
// selectedMedia.removeAll { $0.id == id }
// }
// }
2024-07-04 08:47:08 +00:00
}
2024-07-03 13:47:25 +00:00
} else {
ZStack {
Rectangle()
2024-07-04 08:21:12 +00:00
.fill(Color.Material.Background.light)
2024-07-03 13:47:25 +00:00
.overlay {
ProgressView()
}
2024-07-10 17:49:36 +00:00
.frame(width: Const.galleryGridSize, height: Const.galleryGridSize)
2024-07-03 13:47:25 +00:00
}
}
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) {
uiView.previewLayer?.frame = uiView.bounds
}
}
2024-07-04 10:40:27 +00:00
struct CameraPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType
2024-07-10 17:49:36 +00:00
var completionHandler: (Data, SharingCameraMediaType) -> Void
2024-07-04 10:40:27 +00:00
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = sourceType
picker.delegate = context.coordinator
picker.mediaTypes = [UTType.movie.identifier, UTType.image.identifier]
picker.videoQuality = .typeHigh
2024-07-10 17:49:36 +00:00
picker.videoMaximumDuration = Const.videoDurationLimit
2024-07-04 10:40:27 +00:00
picker.view.backgroundColor = .clear
return picker
}
func updateUIViewController(_: UIImagePickerController, context _: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: CameraPicker
init(_ parent: CameraPicker) {
self.parent = parent
}
func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
2024-07-09 12:43:38 +00:00
// swiftlint:disable:next force_cast
let mediaType = info[.mediaType] as! String
2024-07-09 12:37:51 +00:00
2024-07-10 17:49:36 +00:00
if mediaType == UTType.image.identifier {
if let image = info[.originalImage] as? UIImage {
let data = image.jpegData(compressionQuality: 1.0) ?? Data()
parent.completionHandler(data, .photo)
}
} else if mediaType == UTType.movie.identifier {
if let url = info[.mediaURL] as? URL {
let data = try? Data(contentsOf: url)
parent.completionHandler(data ?? Data(), .video)
}
}
2024-07-04 10:40:27 +00:00
}
}
}