diff --git a/SnapSafe/Screens/AppNavigation.swift b/SnapSafe/Screens/AppNavigation.swift index 8c17821..e3c0cb4 100644 --- a/SnapSafe/Screens/AppNavigation.swift +++ b/SnapSafe/Screens/AppNavigation.swift @@ -16,6 +16,8 @@ enum AppDestination: Hashable { case pinSetup case pinVerification case camera + case photoDetail(allPhotos: [PhotoDef], initialIndex: Int) + case photoInfo(PhotoDef) case photoObfuscation(PhotoDef) case poisonPillSetupWizard } @@ -85,6 +87,8 @@ extension AppDestination: Identifiable { case .pinSetup: return "pinSetup" case .pinVerification: return "pinVerification" case .camera: return "camera" + case .photoDetail(_, let initialIndex): return "photoDetail_\(initialIndex)" + case .photoInfo(let photoDef): return "photoInfo_\(photoDef.photoName)" case .photoObfuscation(let photoDef): return "photoObfuscation_\(photoDef.photoName)" case .poisonPillSetupWizard: return "poisonPillSetupWizard" } diff --git a/SnapSafe/Screens/Camera/CameraContainerView.swift b/SnapSafe/Screens/Camera/CameraContainerView.swift index 953f54b..c2b8f30 100644 --- a/SnapSafe/Screens/Camera/CameraContainerView.swift +++ b/SnapSafe/Screens/Camera/CameraContainerView.swift @@ -128,7 +128,7 @@ struct CameraContainerView: View { HStack { Button(action: { - nav.presentFullScreenCover(.gallery) + nav.navigate(to:.gallery) }) { ZStack { Image(systemName: "photo.on.rectangle") @@ -176,7 +176,7 @@ struct CameraContainerView: View { Spacer() Button(action: { - nav.presentSheet(.settings) + nav.navigate(to:.settings) }) { Image(systemName: "gear") .font(.system(size: 24)) diff --git a/SnapSafe/Screens/Camera/CameraViewModel.swift b/SnapSafe/Screens/Camera/CameraViewModel.swift index 454935f..fa35ced 100644 --- a/SnapSafe/Screens/Camera/CameraViewModel.swift +++ b/SnapSafe/Screens/Camera/CameraViewModel.swift @@ -119,6 +119,12 @@ class CameraViewModel: NSObject, ObservableObject { name: UIApplication.willEnterForegroundNotification, object: nil ) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleAppWillResignActive), + name: UIApplication.willResignActiveNotification, + object: nil + ) } deinit { @@ -132,6 +138,11 @@ class CameraViewModel: NSObject, ObservableObject { resetZoomLevel() } + @objc private func handleAppWillResignActive() { + Logger.camera.info("App will resign active, stopping camera") + stopCameraSession() + } + func restartCameraSessionIfNeeded() { let session = self.session if !session.isRunning { @@ -141,6 +152,16 @@ class CameraViewModel: NSObject, ObservableObject { } } } + + func stopCameraSession() { + let session = self.session + if session.isRunning { + Logger.camera.info("Stopping camera session") + DispatchQueue.global(qos: .userInitiated).async { + session.stopRunning() + } + } + } func checkAndSetupCamera() async { diff --git a/SnapSafe/Screens/ContentView.swift b/SnapSafe/Screens/ContentView.swift index a956c8e..aa44e3f 100644 --- a/SnapSafe/Screens/ContentView.swift +++ b/SnapSafe/Screens/ContentView.swift @@ -31,14 +31,16 @@ struct ContentView: View { .navigationBarHidden(true) .navigationDestination(for: AppDestination.self) { destination in navigationDestinationView(for: destination) - .navigationBarHidden(destination != .gallery) + .navigationBarHidden(shouldHideNavigationBar(for: destination)) } } .sheet(item: $nav.presentedSheet) { destination in navigationDestinationView(for: destination) + .securityManaged() } .fullScreenCover(item: $nav.presentedFullScreenCover) { destination in navigationDestinationView(for: destination) + .securityManaged() } .securityManaged() .onAppear { @@ -78,8 +80,19 @@ struct ContentView: View { } } + // MARK: - Navigation Helper Methods + + private func shouldHideNavigationBar(for destination: AppDestination) -> Bool { + switch destination { + case .gallery, .photoObfuscation, .settings: + return false + default: + return true + } + } + // MARK: - Navigation Destination Views - + @ViewBuilder private func navigationDestinationView(for destination: AppDestination) -> some View { switch destination { @@ -87,7 +100,7 @@ struct ContentView: View { SettingsView() case .gallery: SecureGalleryView(onDismiss: { - nav.dismissFullScreenCover() + nav.navigateBack() }) case .pinSetup: PINSetupIntroView() @@ -95,6 +108,15 @@ struct ContentView: View { PINVerificationView() case .camera: CameraContainerView() + case .photoDetail(let allPhotos, let initialIndex): + EnhancedPhotoDetailView( + allPhotos: allPhotos, + initialIndex: initialIndex, + onDelete: nil, + onDismiss: nil + ) + case .photoInfo(let photoDef): + ImageInfoView(photoDef: photoDef) case .photoObfuscation(let photoDef): PhotoObfuscationView(photoDef: photoDef, navigator: nav) case .poisonPillSetupWizard: diff --git a/SnapSafe/Screens/Gallery/SecureGalleryView.swift b/SnapSafe/Screens/Gallery/SecureGalleryView.swift index 4428726..9c9b896 100644 --- a/SnapSafe/Screens/Gallery/SecureGalleryView.swift +++ b/SnapSafe/Screens/Gallery/SecureGalleryView.swift @@ -29,7 +29,8 @@ struct SecureGalleryView: View { @AppStorage("showFaceDetection") private var showFaceDetection = true // Using AppStorage to share with Settings @StateObject private var viewModel: SecureGalleryViewModel @Environment(\.dismiss) private var dismiss - + @EnvironmentObject private var nav: AppNavigationState + // Callback for dismissing the gallery let onDismiss: (() -> Void)? @@ -47,8 +48,7 @@ struct SecureGalleryView: View { var body: some View { - NavigationStack { - ZStack { + ZStack { Group { if viewModel.photos.isEmpty { EmptyGalleryView(onDismiss: { @@ -83,21 +83,22 @@ struct SecureGalleryView: View { .navigationTitle(viewModel.navigationTitle) .navigationBarTitleDisplayMode(.inline) .toolbar { - // Back button in the leading position - ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - if viewModel.isSelectingDecoys { + // Back button in the leading position (only for decoy selection mode) + if viewModel.isSelectingDecoys { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: { viewModel.exitDecoyMode() - } - onDismiss?() - dismiss() - }) { - HStack { - Image(systemName: "chevron.left") + onDismiss?() + dismiss() + }) { + HStack { + Image(systemName: "chevron.left") + Text("Back") + } } } } - + // Action buttons in the trailing position (simplified for top toolbar) ToolbarItem(placement: .navigationBarTrailing) { HStack(spacing: 16) { @@ -192,30 +193,15 @@ struct SecureGalleryView: View { } .onAppear(perform: viewModel.onAppear) .onChange(of: viewModel.selectedPhoto) { _, newValue in - viewModel.onSelectedPhotoChange(newValue) - } - .fullScreenCover(item: $viewModel.selectedPhoto) { photoDef in + if let photoDef = newValue { // Find the index of the selected photo in the photos array if let initialIndex = viewModel.photos.firstIndex(where: { $0.photoName == photoDef.photoName }) { - EnhancedPhotoDetailView( - allPhotos: viewModel.photos, - initialIndex: initialIndex, - onDelete: { _ in viewModel.onAppear() }, - onDismiss: { - viewModel.clearMemoryForAllPhotos() - } - ) - } else { - // Fallback if photo not found in array - PhotoDetailView( - photo: photoDef, - onDelete: { _ in viewModel.onAppear() }, - onDismiss: { - viewModel.clearMemoryForPhoto(photoDef) - } - ) + nav.navigate(to: .photoDetail(allPhotos: viewModel.photos, initialIndex: initialIndex)) } + // Reset selectedPhoto so it can be selected again + viewModel.selectedPhoto = nil } + } .alert( viewModel.deleteAlertTitle, isPresented: $viewModel.showDeleteConfirmation, @@ -256,7 +242,6 @@ struct SecureGalleryView: View { } ) } - } // Photo grid subview private var photosGridView: some View { diff --git a/SnapSafe/Screens/PhotoDetail/EnhancedPhotoDetailView.swift b/SnapSafe/Screens/PhotoDetail/EnhancedPhotoDetailView.swift index 983f43f..d6b9a8a 100644 --- a/SnapSafe/Screens/PhotoDetail/EnhancedPhotoDetailView.swift +++ b/SnapSafe/Screens/PhotoDetail/EnhancedPhotoDetailView.swift @@ -109,10 +109,14 @@ struct EnhancedPhotoDetailView: View { Spacer() if viewModel.currentIndex < viewModel.photoFiles.count { PhotoControlsView( - onInfo: { viewModel.showImageInfo = true }, + onInfo: { + if let current = viewModel.currentPhotoDef { + nav.presentSheet(.photoInfo(current)) + } + }, onObfuscate: { if let current = viewModel.currentPhotoDef { - nav.presentedFullScreenCover = .photoObfuscation(current) + nav.navigate(to: .photoObfuscation(current)) } }, onShare: { viewModel.shareCurrentPhoto() }, @@ -173,10 +177,5 @@ struct EnhancedPhotoDetailView: View { Text("Are you sure you want to delete this photo? This action cannot be undone.") } ) - .sheet(isPresented: $viewModel.showImageInfo) { - if let photoDef = viewModel.currentPhotoDef { - ImageInfoView(photoDef: photoDef) - } - } } } diff --git a/SnapSafe/Screens/PhotoObfuscation/PhotoObfuscationView.swift b/SnapSafe/Screens/PhotoObfuscation/PhotoObfuscationView.swift index 488018d..8218e92 100644 --- a/SnapSafe/Screens/PhotoObfuscation/PhotoObfuscationView.swift +++ b/SnapSafe/Screens/PhotoObfuscation/PhotoObfuscationView.swift @@ -15,53 +15,51 @@ struct PhotoObfuscationView: View { _viewModel = StateObject(wrappedValue: PhotoObfuscationViewModel( photoDef: photoDef, - onSave: { _ in navigator.presentedFullScreenCover = nil }, - onDismiss: { navigator.presentedFullScreenCover = nil } + onSave: { _ in navigator.navigateBack() }, + onDismiss: { navigator.navigateBack() } ) ) } - + private func onDismiss() { - nav.presentedFullScreenCover = nil + nav.navigateBack() } var body: some View { - NavigationView { - ZStack { - Color.black - .ignoresSafeArea() - - if viewModel.isImageLoading { - ProgressView("Loading image...") - .progressViewStyle(CircularProgressViewStyle(tint: .white)) - .foregroundColor(.white) - } else { - imageContent - } - } - .navigationTitle("Photo Obfuscation") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { - viewModel.cancel() - onDismiss() - } + ZStack { + Color.black + .ignoresSafeArea() + + if viewModel.isImageLoading { + ProgressView("Loading image...") + .progressViewStyle(CircularProgressViewStyle(tint: .white)) .foregroundColor(.white) + } else { + imageContent + } + } + .navigationTitle("Photo Obfuscation") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Cancel") { + viewModel.cancel() + onDismiss() } - - ToolbarItem(placement: .navigationBarTrailing) { - Button("Save") { - viewModel.saveChanges() - onDismiss() - } - .foregroundColor(.blue) - .fontWeight(.semibold) + .foregroundColor(.white) + } + + ToolbarItem(placement: .navigationBarTrailing) { + Button("Save") { + viewModel.saveChanges() + onDismiss() } + .foregroundColor(.blue) + .fontWeight(.semibold) } - .toolbarBackground(Color.black, for: .navigationBar) - .toolbarBackground(.visible, for: .navigationBar) } + .toolbarBackground(Color.black, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) .alert("Obscure Faces", isPresented: $viewModel.showObscureConfirmation) { Button("Cancel", role: .cancel) { } Button(viewModel.maskActionTitle) { diff --git a/SnapSafe/Screens/Settings/SettingsView.swift b/SnapSafe/Screens/Settings/SettingsView.swift index 450e2fa..3ccbeb7 100644 --- a/SnapSafe/Screens/Settings/SettingsView.swift +++ b/SnapSafe/Screens/Settings/SettingsView.swift @@ -26,8 +26,7 @@ struct SettingsView: View { @EnvironmentObject private var nav: AppNavigationState var body: some View { - NavigationView { - List { + List { // ABOUT SECTION Section { @@ -209,6 +208,5 @@ struct SettingsView: View { }) } } - } } } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 59250ae..2124c6f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -117,7 +117,7 @@ platform :ios do upload_to_app_store( skip_screenshots: true, skip_metadata: true, - submit_for_review: true, + submit_for_review: false, force: false, precheck_include_in_app_purchases: false )