diff --git a/ConversationsClassic/AppData/Store/ConversationStore.swift b/ConversationsClassic/AppData/Store/ConversationStore.swift index e31a841..592f328 100644 --- a/ConversationsClassic/AppData/Store/ConversationStore.swift +++ b/ConversationsClassic/AppData/Store/ConversationStore.swift @@ -1,13 +1,17 @@ +import AVFoundation import Combine import Foundation import GRDB +import Photos @MainActor final class ConversationStore: ObservableObject { @Published private(set) var messages: [Message] = [] @Published var replyText = "" - private(set) var roster: Roster + @Published var cameraAccessGranted = false + @Published var galleryAccessGranted = false + private(set) var roster: Roster private let client: Client private let blockSize = Const.messagesPageSize private let messagesMax = Const.messagesMaxSize @@ -49,6 +53,27 @@ extension ConversationStore { } } +extension ConversationStore { + func checkCameraAuthorization() async { + let status = AVCaptureDevice.authorizationStatus(for: .video) + var isAuthorized = status == .authorized + if status == .notDetermined { + isAuthorized = await AVCaptureDevice.requestAccess(for: .video) + } + cameraAccessGranted = isAuthorized + } + + func checkGalleryAuthorization() async { + let status = PHPhotoLibrary.authorizationStatus() + var isAuthorized = status == .authorized + if status == .notDetermined { + let req = await PHPhotoLibrary.requestAuthorization(for: .readWrite) + isAuthorized = (req == .authorized) || (req == .limited) + } + galleryAccessGranted = isAuthorized + } +} + private extension ConversationStore { func subscribe() { messagesCancellable = ValueObservation.tracking(Message @@ -67,26 +92,3 @@ private extension ConversationStore { } } } - -// var isAuthorized: Bool { -// get async { -// let status = AVCaptureDevice.authorizationStatus(for: .video) -// -// // Determine if the user previously authorized camera access. -// var isAuthorized = status == .authorized -// -// // If the system hasn't determined the user's authorization status, -// // explicitly prompt them for approval. -// if status == .notDetermined { -// isAuthorized = await AVCaptureDevice.requestAccess(for: .video) -// } -// -// return isAuthorized -// } -// } -// -// -// func setUpCaptureSession() async { -// guard await isAuthorized else { return } -// // Set up the capture session. -// } diff --git a/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/CameraCellPreview.swift b/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/CameraCellPreview.swift index b74c01b..65cf60c 100644 --- a/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/CameraCellPreview.swift +++ b/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/CameraCellPreview.swift @@ -3,11 +3,11 @@ import SwiftUI struct CameraCellPreview: View { @Environment(\.router) var router - @State private var cameraAuthorized = false + @EnvironmentObject var store: ConversationStore var body: some View { Group { - if cameraAuthorized { + if store.cameraAccessGranted { ZStack { CameraView() .aspectRatio(1, contentMode: .fit) @@ -51,16 +51,7 @@ struct CameraCellPreview: View { } } .task { - await checkAuthorization() + await store.checkCameraAuthorization() } } - - private func checkAuthorization() async { - let status = AVCaptureDevice.authorizationStatus(for: .video) - var isAuthorized = status == .authorized - if status == .notDetermined { - isAuthorized = await AVCaptureDevice.requestAccess(for: .video) - } - cameraAuthorized = isAuthorized - } } diff --git a/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/GalleryView.swift b/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/GalleryView.swift new file mode 100644 index 0000000..effc456 --- /dev/null +++ b/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/GalleryView.swift @@ -0,0 +1,104 @@ +import SwiftUI + +struct GalleryView: View { + // @State private var selectedItems: [String] = [] + + var body: some View { + Text("test") + // Group { + // if store.state.sharingState.isGalleryAccessGranted { + // ForEach(store.state.sharingState.galleryItems) { item in + // GridViewItem(item: item, selected: $selectedItems) + // } + // } 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) + // } + // } + // .frame(height: 100) + // } + // } + // } + // } + } +} + +private struct GridViewItem: View { + // let item: SharingGalleryItem + @Binding var selected: [String] + @State var isSelected = false + + var body: some View { + Text("Test") + // if let data = item.thumbnail { + // ZStack { + // Image(uiImage: UIImage(data: data) ?? UIImage()) + // .resizable() + // .aspectRatio(contentMode: .fill) + // .frame(width: Const.galleryGridSize, height: Const.galleryGridSize) + // .clipped() + // if let duration = item.duration { + // VStack { + // Spacer() + // HStack { + // Spacer() + // Text(duration) + // .foregroundColor(.Material.Text.white) + // .font(.sub1) + // .shadow(color: .black, radius: 2) + // .padding(4) + // } + // } + // } + // if isSelected { + // 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() + // } + // } + // } + // .onTapGesture { + // isSelected.toggle() + // if isSelected { + // selected.append(item.id) + // } else { + // selected.removeAll { $0 == item.id } + // } + // } + // } else { + // ZStack { + // Rectangle() + // .fill(Color.Material.Background.light) + // .overlay { + // ProgressView() + // .foregroundColor(.Material.Elements.active) + // } + // .frame(width: Const.galleryGridSize, height: Const.galleryGridSize) + // } + // } + } +} diff --git a/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/MediaPickerView.swift b/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/MediaPickerView.swift index 66accb4..ae7dbba 100644 --- a/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/MediaPickerView.swift +++ b/ConversationsClassic/View/Main/Conversation/Attachments/MediaPicker/MediaPickerView.swift @@ -4,9 +4,6 @@ import Photos import SwiftUI struct MediaPickerView: View { - // @State private var showCameraPicker = false - // @State private var cameraReady = false - @State private var selectedItems: [String] = [] var body: some View { @@ -20,41 +17,9 @@ struct MediaPickerView: View { CameraCellPreview() // For gallery - // if store.state.sharingState.isGalleryAccessGranted { - // ForEach(store.state.sharingState.galleryItems) { item in - // GridViewItem(item: item, selected: $selectedItems) - // } - // } 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) - // } - // } - // .frame(height: 100) - // } - // } - // } + GalleryView() } } - // .fullScreenCover(isPresented: $showCameraPicker) { - // CameraPicker(sourceType: .camera) { data, type in - // store.dispatch(.sharingAction(.cameraCaptured(media: data, type: type))) - // showCameraPicker = false - // store.dispatch(.sharingAction(.showSharing(false))) - // } - // .edgesIgnoringSafeArea(.all) - // } // Send panel Rectangle() @@ -79,82 +44,5 @@ struct MediaPickerView: View { // store.dispatch(.sharingAction(.showSharing(false))) } } - // .onAppear { - // store.dispatch(.sharingAction(.checkCameraAccess)) - // store.dispatch(.sharingAction(.checkGalleryAccess)) - // } - // .onChange(of: store.state.sharingState.isGalleryAccessGranted) { granted in - // if granted { - // store.dispatch(.fileAction(.fetchItemsFromGallery)) - // } - // } - } -} - -private struct GridViewItem: View { - // let item: SharingGalleryItem - @Binding var selected: [String] - @State var isSelected = false - - var body: some View { - Text("Test") - // if let data = item.thumbnail { - // ZStack { - // Image(uiImage: UIImage(data: data) ?? UIImage()) - // .resizable() - // .aspectRatio(contentMode: .fill) - // .frame(width: Const.galleryGridSize, height: Const.galleryGridSize) - // .clipped() - // if let duration = item.duration { - // VStack { - // Spacer() - // HStack { - // Spacer() - // Text(duration) - // .foregroundColor(.Material.Text.white) - // .font(.sub1) - // .shadow(color: .black, radius: 2) - // .padding(4) - // } - // } - // } - // if isSelected { - // 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() - // } - // } - // } - // .onTapGesture { - // isSelected.toggle() - // if isSelected { - // selected.append(item.id) - // } else { - // selected.removeAll { $0 == item.id } - // } - // } - // } else { - // ZStack { - // Rectangle() - // .fill(Color.Material.Background.light) - // .overlay { - // ProgressView() - // .foregroundColor(.Material.Elements.active) - // } - // .frame(width: Const.galleryGridSize, height: Const.galleryGridSize) - // } - // } } } diff --git a/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift b/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift index 14c4280..f02be43 100644 --- a/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift +++ b/ConversationsClassic/View/Main/Conversation/ConversationTextInput.swift @@ -52,6 +52,7 @@ struct ConversationTextInput: View { .tappablePadding(.symmetric(8)) { router.showScreen(.fullScreenCover) { _ in AttachmentPickerScreen() + .environmentObject(conversation) } } TextField("", text: $messageStr, prompt: Text(L10n.Chat.textfieldPrompt).foregroundColor(.Material.Shape.separator))