diff --git a/SelfieRingLight/Features/Camera/ContentView.swift b/SelfieRingLight/Features/Camera/ContentView.swift index 69908e9..01f735e 100644 --- a/SelfieRingLight/Features/Camera/ContentView.swift +++ b/SelfieRingLight/Features/Camera/ContentView.swift @@ -14,50 +14,77 @@ struct ContentView: View { @State private var capturedVideoURL: URL? @State private var showPostCapture = false + /// Ring size clamped to reasonable max + private var effectiveRingSize: CGFloat { + let maxRing = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height) * 0.25 + return min(settings.ringSize, maxRing) + } + var body: some View { - MCamera() - .setCameraScreen { manager, namespace, closeAction in - RingLightCameraScreen( - cameraManager: manager, - namespace: namespace, - closeMCameraAction: closeAction, - settings: settings, - isPremiumUnlocked: premiumManager.isPremiumUnlocked, - onSettingsTapped: { showSettings = true } - ) - } - .onImageCaptured { image, _ in - capturedImage = image - showPostCapture = true - } - .onVideoCaptured { url, _ in - capturedVideoURL = url - showPostCapture = true - } - .startSession() - .ignoresSafeArea() - .sheet(isPresented: $showSettings) { - SettingsView(viewModel: settings, showPaywall: $showPaywall) - } - .sheet(isPresented: $showPaywall) { - ProPaywallView() - } - .fullScreenCover(isPresented: $showPostCapture) { - PostCapturePreviewView( - capturedImage: capturedImage, - capturedVideoURL: capturedVideoURL, - isAutoSaveEnabled: settings.isAutoSaveEnabled, - onRetake: { - capturedImage = nil - capturedVideoURL = nil - showPostCapture = false - }, - onSave: { - saveCapture() - showPostCapture = false + ZStack { + // Ring light background + settings.lightColor + .ignoresSafeArea() + + // MijickCamera with default UI, padded for ring effect + MCamera() + .onImageCaptured { image, _ in + capturedImage = image + showPostCapture = true + } + .onVideoCaptured { url, _ in + capturedVideoURL = url + showPostCapture = true + } + .startSession() + .clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.large)) + .padding(effectiveRingSize) + + // Settings button overlay (top right, safe area aware) + VStack { + HStack { + Spacer() + + Button { + showSettings = true + } label: { + Image(systemName: "gearshape.fill") + .font(.title3) + .foregroundStyle(.white) + .padding(Design.Spacing.medium) + .background(.ultraThinMaterial, in: Circle()) } - ) + .accessibilityLabel("Settings") + } + .padding(.horizontal, effectiveRingSize + Design.Spacing.small) + .padding(.top, Design.Spacing.small) + + Spacer() } + } + .ignoresSafeArea() + .sheet(isPresented: $showSettings) { + SettingsView(viewModel: settings, showPaywall: $showPaywall) + } + .sheet(isPresented: $showPaywall) { + ProPaywallView() + } + .fullScreenCover(isPresented: $showPostCapture) { + PostCapturePreviewView( + capturedImage: capturedImage, + capturedVideoURL: capturedVideoURL, + isAutoSaveEnabled: settings.isAutoSaveEnabled, + onRetake: { + capturedImage = nil + capturedVideoURL = nil + showPostCapture = false + }, + onSave: { + saveCapture() + showPostCapture = false + } + ) + } } // MARK: - Save Capture diff --git a/SelfieRingLight/Features/Camera/RingLightCameraScreen.swift b/SelfieRingLight/Features/Camera/RingLightCameraScreen.swift deleted file mode 100644 index 01dc11e..0000000 --- a/SelfieRingLight/Features/Camera/RingLightCameraScreen.swift +++ /dev/null @@ -1,183 +0,0 @@ -import SwiftUI -import MijickCamera -import Bedrock - -/// Custom MijickCamera screen with ring light effect -struct RingLightCameraScreen: MCameraScreen { - // Required by MCameraScreen protocol - @ObservedObject var cameraManager: CameraManager - let namespace: Namespace.ID - let closeMCameraAction: () -> () - - // Our custom properties - let settings: SettingsViewModel - let isPremiumUnlocked: Bool - let onSettingsTapped: () -> Void - - // MARK: - Layout Constants - - /// Capture button inner padding - private let captureButtonInnerPadding: CGFloat = 8 - - var body: some View { - GeometryReader { geometry in - let safeArea = geometry.safeAreaInsets - - ZStack { - // Ring light background - fills entire screen - settings.lightColor - .ignoresSafeArea() - - // Main content - VStack(spacing: 0) { - // Top control bar - topControlBar - .padding(.top, safeArea.top + Design.Spacing.small) - .padding(.horizontal, Design.Spacing.large) - - // Camera preview - uses native camera aspect ratio - // The ring light is the padding around it - createCameraOutputView() - .clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.large)) - .padding(.horizontal, effectiveRingSize) - .padding(.vertical, Design.Spacing.medium) - .overlay { - // Grid overlay on top of camera - if settings.isGridVisible { - GridOverlay(isVisible: true) - .padding(.horizontal, effectiveRingSize) - .padding(.vertical, Design.Spacing.medium) - } - } - - // Bottom control bar - bottomControlBar - .padding(.bottom, safeArea.bottom + Design.Spacing.small) - .padding(.horizontal, Design.Spacing.xLarge) - } - } - .ignoresSafeArea() - } - } - - // MARK: - Ring Size - - private var effectiveRingSize: CGFloat { - min(settings.ringSize, maxAllowedRingSize) - } - - private var maxAllowedRingSize: CGFloat { - let screenSize = UIScreen.main.bounds.size - let smallerDimension = min(screenSize.width, screenSize.height) - // Allow ring to take up to 30% of the smaller dimension - return smallerDimension * 0.3 - } - - // MARK: - Top Control Bar - - private var topControlBar: some View { - HStack { - // Grid toggle - controlButton( - icon: settings.isGridVisible ? "square.grid.3x3.fill" : "square.grid.3x3", - accessibilityLabel: "Grid", - accessibilityValue: settings.isGridVisible ? "On" : "Off", - accessibilityHint: "Toggles the rule of thirds grid overlay" - ) { - settings.isGridVisible.toggle() - } - - Spacer() - - // Settings - controlButton( - icon: "gearshape.fill", - accessibilityLabel: "Settings", - accessibilityHint: "Opens settings" - ) { - onSettingsTapped() - } - } - } - - // MARK: - Bottom Control Bar - - private var bottomControlBar: some View { - HStack { - // Camera flip - left side - controlButton( - icon: "arrow.triangle.2.circlepath.camera.fill", - size: .title2, - accessibilityLabel: "Switch Camera", - accessibilityHint: "Switches between front and back camera" - ) { - Task { - try? await setCameraPosition(cameraPosition == .front ? .back : .front) - } - } - - Spacer() - - // Capture button - center - captureButton - - Spacer() - - // Placeholder for symmetry - right side - Color.clear - .frame(width: 44, height: 44) - } - } - - // MARK: - Control Button Helper - - private func controlButton( - icon: String, - size: Font = .body, - accessibilityLabel: String, - accessibilityValue: String? = nil, - accessibilityHint: String? = nil, - action: @escaping () -> Void - ) -> some View { - Button(action: action) { - Image(systemName: icon) - .font(size) - .foregroundStyle(.white) - .padding(Design.Spacing.small) - .background(.ultraThinMaterial, in: Circle()) - } - .accessibilityLabel(accessibilityLabel) - .accessibilityValue(accessibilityValue ?? "") - .accessibilityHint(accessibilityHint ?? "") - } - - // MARK: - Capture Button - - private var captureButton: some View { - Button { - captureOutput() - } label: { - ZStack { - // Outer white ring - Circle() - .fill(.white) - .frame(width: Design.Capture.buttonSize, height: Design.Capture.buttonSize) - - // Colored border matching ring light - Circle() - .strokeBorder(settings.lightColor, lineWidth: Design.LineWidth.thick) - .frame(width: Design.Capture.buttonSize, height: Design.Capture.buttonSize) - - // Inner white circle - Circle() - .fill(.white) - .frame( - width: Design.Capture.buttonSize - captureButtonInnerPadding, - height: Design.Capture.buttonSize - captureButtonInnerPadding - ) - } - } - .accessibilityLabel("Capture") - .accessibilityHint("Takes a photo") - } -} diff --git a/SelfieRingLight/Resources/Localizable.xcstrings b/SelfieRingLight/Resources/Localizable.xcstrings index 31fd8e6..9a04c3c 100644 --- a/SelfieRingLight/Resources/Localizable.xcstrings +++ b/SelfieRingLight/Resources/Localizable.xcstrings @@ -113,10 +113,6 @@ "comment" : "The title of the \"Go Pro\" button in the Pro paywall.", "isCommentAutoGenerated" : true }, - "Grid" : { - "comment" : "A button that toggles the visibility of a grid overlay in the camera view.", - "isCommentAutoGenerated" : true - }, "Grid Overlay" : { "comment" : "Text displayed in a settings toggle for showing a grid overlay to help compose your shot.", "isCommentAutoGenerated" : true @@ -148,18 +144,10 @@ "comment" : "The accessibility value for the grid toggle when it is off.", "isCommentAutoGenerated" : true }, - "On" : { - "comment" : "A label that describes a setting as \"On\".", - "isCommentAutoGenerated" : true - }, "Open Source Licenses" : { "comment" : "A heading displayed above a list of open source licenses used in the app.", "isCommentAutoGenerated" : true }, - "Opens settings" : { - "comment" : "A hint describing the action of tapping the settings button.", - "isCommentAutoGenerated" : true - }, "Opens upgrade options" : { "comment" : "An accessibility hint for the \"Upgrade to Pro\" button that indicates it opens upgrade options.", "isCommentAutoGenerated" : true @@ -259,14 +247,6 @@ } } }, - "Switch Camera" : { - "comment" : "A button that switches between the front and back camera.", - "isCommentAutoGenerated" : true - }, - "Switches between front and back camera" : { - "comment" : "A hint that describes the functionality of the \"Switch Camera\" button in the camera screen.", - "isCommentAutoGenerated" : true - }, "Sync Now" : { "comment" : "A button label that triggers a sync action.", "isCommentAutoGenerated" : true @@ -294,10 +274,6 @@ "comment" : "A description of the third-party libraries used in this app.", "isCommentAutoGenerated" : true }, - "Toggles the rule of thirds grid overlay" : { - "comment" : "An accessibility hint for the grid toggle button in the top control bar of the ring light camera screen.", - "isCommentAutoGenerated" : true - }, "True Mirror" : { "comment" : "Title of a toggle in the settings view that allows the user to flip the camera preview.", "isCommentAutoGenerated" : true