another.im-ios/ConversationsClassic/View/Screens/Attachments/AttachmentMediaPickerView.swift

247 lines
8.3 KiB
Swift
Raw Normal View History

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
2024-07-03 13:47:25 +00:00
@State private var photos = [PhotoView]()
2024-07-03 11:50:59 +00:00
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 {
2024-07-03 13:47:25 +00:00
ForEach(photos) { photo in
photo
2024-07-03 11:50:59 +00:00
}
} 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 {
2024-07-03 13:47:25 +00:00
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) {
checkCameraAccess()
checkGalleryAccess()
}
2024-07-03 11:50:59 +00:00
}
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
2024-07-03 13:47:25 +00:00
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))
2024-07-03 11:50:59 +00:00
}
}
2024-07-03 13:47:25 +00:00
})
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
}
2024-07-03 13:47:25 +00:00
}
private struct PhotoView: Identifiable, View {
let id = UUID()
let gridSize: CGFloat
@State private var image: UIImage
@State private var ready = false
2024-07-03 11:50:59 +00:00
2024-07-03 13:47:25 +00:00
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
}
})
}
}
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
}
}