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:
Matt Bruce 2026-01-02 16:51:29 -06:00
parent 7e5c4a021f
commit 9250a44a65
3 changed files with 68 additions and 248 deletions

View File

@ -14,18 +14,20 @@ struct ContentView: View {
@State private var capturedVideoURL: URL?
@State private var showPostCapture = false
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 }
)
/// 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 {
ZStack {
// Ring light background
settings.lightColor
.ignoresSafeArea()
// MijickCamera with default UI, padded for ring effect
MCamera()
.onImageCaptured { image, _ in
capturedImage = image
showPostCapture = true
@ -35,6 +37,31 @@ struct ContentView: View {
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)

View File

@ -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")
}
}

View File

@ -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