Use MijickCamera's default UI with ring light overlay
- Remove custom RingLightCameraScreen - Use MijickCamera's built-in camera UI (capture, flip, etc.) - Ring light is now the background with MCamera padded inside - Only overlay a Settings button on top - Much simpler and uses the package's tested UI
This commit is contained in:
parent
7e5c4a021f
commit
9250a44a65
@ -14,50 +14,77 @@ struct ContentView: View {
|
|||||||
@State private var capturedVideoURL: URL?
|
@State private var capturedVideoURL: URL?
|
||||||
@State private var showPostCapture = false
|
@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 {
|
var body: some View {
|
||||||
MCamera()
|
ZStack {
|
||||||
.setCameraScreen { manager, namespace, closeAction in
|
// Ring light background
|
||||||
RingLightCameraScreen(
|
settings.lightColor
|
||||||
cameraManager: manager,
|
.ignoresSafeArea()
|
||||||
namespace: namespace,
|
|
||||||
closeMCameraAction: closeAction,
|
// MijickCamera with default UI, padded for ring effect
|
||||||
settings: settings,
|
MCamera()
|
||||||
isPremiumUnlocked: premiumManager.isPremiumUnlocked,
|
.onImageCaptured { image, _ in
|
||||||
onSettingsTapped: { showSettings = true }
|
capturedImage = image
|
||||||
)
|
showPostCapture = true
|
||||||
}
|
}
|
||||||
.onImageCaptured { image, _ in
|
.onVideoCaptured { url, _ in
|
||||||
capturedImage = image
|
capturedVideoURL = url
|
||||||
showPostCapture = true
|
showPostCapture = true
|
||||||
}
|
}
|
||||||
.onVideoCaptured { url, _ in
|
.startSession()
|
||||||
capturedVideoURL = url
|
.clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.large))
|
||||||
showPostCapture = true
|
.padding(effectiveRingSize)
|
||||||
}
|
|
||||||
.startSession()
|
// Settings button overlay (top right, safe area aware)
|
||||||
.ignoresSafeArea()
|
VStack {
|
||||||
.sheet(isPresented: $showSettings) {
|
HStack {
|
||||||
SettingsView(viewModel: settings, showPaywall: $showPaywall)
|
Spacer()
|
||||||
}
|
|
||||||
.sheet(isPresented: $showPaywall) {
|
Button {
|
||||||
ProPaywallView()
|
showSettings = true
|
||||||
}
|
} label: {
|
||||||
.fullScreenCover(isPresented: $showPostCapture) {
|
Image(systemName: "gearshape.fill")
|
||||||
PostCapturePreviewView(
|
.font(.title3)
|
||||||
capturedImage: capturedImage,
|
.foregroundStyle(.white)
|
||||||
capturedVideoURL: capturedVideoURL,
|
.padding(Design.Spacing.medium)
|
||||||
isAutoSaveEnabled: settings.isAutoSaveEnabled,
|
.background(.ultraThinMaterial, in: Circle())
|
||||||
onRetake: {
|
|
||||||
capturedImage = nil
|
|
||||||
capturedVideoURL = nil
|
|
||||||
showPostCapture = false
|
|
||||||
},
|
|
||||||
onSave: {
|
|
||||||
saveCapture()
|
|
||||||
showPostCapture = false
|
|
||||||
}
|
}
|
||||||
)
|
.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
|
// MARK: - Save Capture
|
||||||
|
|||||||
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -113,10 +113,6 @@
|
|||||||
"comment" : "The title of the \"Go Pro\" button in the Pro paywall.",
|
"comment" : "The title of the \"Go Pro\" button in the Pro paywall.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Grid" : {
|
|
||||||
"comment" : "A button that toggles the visibility of a grid overlay in the camera view.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Grid Overlay" : {
|
"Grid Overlay" : {
|
||||||
"comment" : "Text displayed in a settings toggle for showing a grid overlay to help compose your shot.",
|
"comment" : "Text displayed in a settings toggle for showing a grid overlay to help compose your shot.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -148,18 +144,10 @@
|
|||||||
"comment" : "The accessibility value for the grid toggle when it is off.",
|
"comment" : "The accessibility value for the grid toggle when it is off.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"On" : {
|
|
||||||
"comment" : "A label that describes a setting as \"On\".",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Open Source Licenses" : {
|
"Open Source Licenses" : {
|
||||||
"comment" : "A heading displayed above a list of open source licenses used in the app.",
|
"comment" : "A heading displayed above a list of open source licenses used in the app.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Opens settings" : {
|
|
||||||
"comment" : "A hint describing the action of tapping the settings button.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Opens upgrade options" : {
|
"Opens upgrade options" : {
|
||||||
"comment" : "An accessibility hint for the \"Upgrade to Pro\" button that indicates it opens upgrade options.",
|
"comment" : "An accessibility hint for the \"Upgrade to Pro\" button that indicates it opens upgrade options.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"Sync Now" : {
|
||||||
"comment" : "A button label that triggers a sync action.",
|
"comment" : "A button label that triggers a sync action.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@ -294,10 +274,6 @@
|
|||||||
"comment" : "A description of the third-party libraries used in this app.",
|
"comment" : "A description of the third-party libraries used in this app.",
|
||||||
"isCommentAutoGenerated" : true
|
"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" : {
|
"True Mirror" : {
|
||||||
"comment" : "Title of a toggle in the settings view that allows the user to flip the camera preview.",
|
"comment" : "Title of a toggle in the settings view that allows the user to flip the camera preview.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user