From 052e4715dcc79f55f402d992d68f43791b871106 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 1 Feb 2026 18:43:07 -0600 Subject: [PATCH] Signed-off-by: Matt Bruce --- .../UserInterfaceState.xcuserstate | Bin 0 -> 10960 bytes .../Features/Camera/Views/ContentView.swift | 10 +++-- .../Camera/Views/CustomCameraScreen.swift | 35 +++++++++++------- 3 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 SelfieCam.xcodeproj/project.xcworkspace/xcuserdata/mattbruce.xcuserdatad/UserInterfaceState.xcuserstate diff --git a/SelfieCam.xcodeproj/project.xcworkspace/xcuserdata/mattbruce.xcuserdatad/UserInterfaceState.xcuserstate b/SelfieCam.xcodeproj/project.xcworkspace/xcuserdata/mattbruce.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..1f5e7330ae2809ce5b4a39fa4366112c7dea4ca3 GIT binary patch literal 10960 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 nQe} Void - // Only compare sessionKey for equality - ignore settings and callback changes + // Only compare sessionKey and cameraPosition for equality static func == (lhs: CameraContainerView, rhs: CameraContainerView) -> Bool { - lhs.sessionKey == rhs.sessionKey + lhs.sessionKey == rhs.sessionKey && lhs.cameraPosition == rhs.cameraPosition } var body: some View { diff --git a/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift b/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift index 3347afd..00ff53a 100644 --- a/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift +++ b/SelfieCam/Features/Camera/Views/CustomCameraScreen.swift @@ -341,21 +341,30 @@ struct CustomCameraScreen: MCameraScreen { print("performActualCapture called - shouldUseCustomScreenFlash: \(shouldUseCustomScreenFlash)") if shouldUseCustomScreenFlash { - // Save original brightness and boost to max - originalBrightness = UIScreen.main.brightness - UIScreen.main.brightness = 1.0 - - // Show flash overlay - isShowingScreenFlash = true - - // Wait for camera to adjust to bright screen, then capture + // Emulate iPhone Retina Flash behavior: + // 1. Pre-flash (briefly show flash to let camera adjust exposure) + // 2. Sustain (peak brightness for capture) + // 3. Capture (timed during sustain) + Task { @MainActor in - try? await Task.sleep(for: .milliseconds(150)) - print("Calling captureOutput() with custom flash") + // Save original brightness + originalBrightness = UIScreen.main.brightness + + // 1. Pre-flash: Boost brightness and show overlay briefly + UIScreen.main.brightness = 1.0 + isShowingScreenFlash = true + + // Brief pre-flash duration to let auto-exposure settle + try? await Task.sleep(for: .milliseconds(200)) + + // 2. Sustain & 3. Capture + print("Calling captureOutput() during sustain phase") captureOutput() - - // Keep flash visible briefly after capture - try? await Task.sleep(for: .milliseconds(100)) + + // Keep flash visible briefly after capture to avoid abrupt cut-off + try? await Task.sleep(for: .milliseconds(150)) + + // Cleanup isShowingScreenFlash = false UIScreen.main.brightness = originalBrightness }