Features: - Full-screen PostCapturePreviewView after photo/video capture - Auto-save to Photo Library (on by default, configurable in Settings) - Toast notification when saved - Retake button to discard and return to camera - Share button with native iOS Share Sheet - Edit mode with smoothing and glow intensity sliders - Premium tools teaser in edit view - Video/boomerang auto-playback with loop support Settings: - Added 'Auto-Save' toggle in Capture section - Syncs across devices via iCloud Architecture: - CapturedMedia enum for photo/video/boomerang types - ShareSheet UIViewControllerRepresentable wrapper - Toast system in CameraViewModel
140 lines
4.4 KiB
Swift
140 lines
4.4 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
import Bedrock
|
|
|
|
// MARK: - Synced Settings Data
|
|
|
|
/// Settings data structure that syncs across all devices via iCloud.
|
|
/// Conforms to `PersistableData` for use with Bedrock's `CloudSyncManager`.
|
|
struct SyncedSettings: PersistableData, Sendable {
|
|
|
|
// MARK: - PersistableData Requirements
|
|
|
|
static var dataIdentifier: String { "selfieRingLightSettings" }
|
|
|
|
static var empty: SyncedSettings {
|
|
SyncedSettings()
|
|
}
|
|
|
|
/// Sync priority based on modification count - higher means more changes made.
|
|
/// This ensures the most actively used device's settings win in conflicts.
|
|
var syncPriority: Int {
|
|
modificationCount
|
|
}
|
|
|
|
var lastModified: Date = .now
|
|
|
|
// MARK: - Settings Properties
|
|
|
|
/// How many times settings have been modified (for sync priority)
|
|
var modificationCount: Int = 0
|
|
|
|
/// Ring border size in points (stored as Double for Codable compatibility)
|
|
var ringSizeValue: Double = 40
|
|
|
|
/// ID of the selected light color preset
|
|
var lightColorId: String = "pureWhite"
|
|
|
|
/// Ring light intensity/opacity (0.5 to 1.0)
|
|
var lightIntensity: Double = 1.0
|
|
|
|
/// Whether front flash is enabled (hides preview during capture)
|
|
var isFrontFlashEnabled: Bool = true
|
|
|
|
/// Whether the camera preview is flipped to show a true mirror
|
|
var isMirrorFlipped: Bool = false
|
|
|
|
/// Whether skin smoothing filter is enabled
|
|
var isSkinSmoothingEnabled: Bool = true
|
|
|
|
/// Selected self-timer option raw value
|
|
var selectedTimerRaw: String = "off"
|
|
|
|
/// Whether the grid overlay is visible
|
|
var isGridVisible: Bool = false
|
|
|
|
/// Current camera zoom factor
|
|
var currentZoomFactor: Double = 1.0
|
|
|
|
/// Selected capture mode raw value
|
|
var selectedCaptureModeRaw: String = "photo"
|
|
|
|
/// Whether captures are auto-saved to Photo Library
|
|
var isAutoSaveEnabled: Bool = true
|
|
|
|
// MARK: - Computed Properties
|
|
|
|
/// Ring size as CGFloat (convenience accessor)
|
|
var ringSize: CGFloat {
|
|
get { CGFloat(ringSizeValue) }
|
|
set { ringSizeValue = Double(newValue) }
|
|
}
|
|
|
|
// MARK: - Initialization
|
|
|
|
init() {}
|
|
|
|
init(
|
|
ringSize: CGFloat,
|
|
lightColorId: String,
|
|
lightIntensity: Double,
|
|
isFrontFlashEnabled: Bool,
|
|
isMirrorFlipped: Bool,
|
|
isSkinSmoothingEnabled: Bool,
|
|
selectedTimerRaw: String,
|
|
isGridVisible: Bool,
|
|
currentZoomFactor: Double,
|
|
selectedCaptureModeRaw: String,
|
|
modificationCount: Int = 0
|
|
) {
|
|
self.ringSizeValue = Double(ringSize)
|
|
self.lightColorId = lightColorId
|
|
self.lightIntensity = lightIntensity
|
|
self.isFrontFlashEnabled = isFrontFlashEnabled
|
|
self.isMirrorFlipped = isMirrorFlipped
|
|
self.isSkinSmoothingEnabled = isSkinSmoothingEnabled
|
|
self.selectedTimerRaw = selectedTimerRaw
|
|
self.isGridVisible = isGridVisible
|
|
self.currentZoomFactor = currentZoomFactor
|
|
self.selectedCaptureModeRaw = selectedCaptureModeRaw
|
|
self.modificationCount = modificationCount
|
|
self.lastModified = .now
|
|
}
|
|
|
|
// MARK: - Codable
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case modificationCount
|
|
case lastModified
|
|
case ringSizeValue
|
|
case lightColorId
|
|
case lightIntensity
|
|
case isFrontFlashEnabled
|
|
case isMirrorFlipped
|
|
case isSkinSmoothingEnabled
|
|
case selectedTimerRaw
|
|
case isGridVisible
|
|
case currentZoomFactor
|
|
case selectedCaptureModeRaw
|
|
case isAutoSaveEnabled
|
|
}
|
|
}
|
|
|
|
// MARK: - Equatable
|
|
|
|
extension SyncedSettings: Equatable {
|
|
static func == (lhs: SyncedSettings, rhs: SyncedSettings) -> Bool {
|
|
lhs.ringSizeValue == rhs.ringSizeValue &&
|
|
lhs.lightColorId == rhs.lightColorId &&
|
|
lhs.lightIntensity == rhs.lightIntensity &&
|
|
lhs.isFrontFlashEnabled == rhs.isFrontFlashEnabled &&
|
|
lhs.isMirrorFlipped == rhs.isMirrorFlipped &&
|
|
lhs.isSkinSmoothingEnabled == rhs.isSkinSmoothingEnabled &&
|
|
lhs.selectedTimerRaw == rhs.selectedTimerRaw &&
|
|
lhs.isGridVisible == rhs.isGridVisible &&
|
|
lhs.currentZoomFactor == rhs.currentZoomFactor &&
|
|
lhs.selectedCaptureModeRaw == rhs.selectedCaptureModeRaw &&
|
|
lhs.isAutoSaveEnabled == rhs.isAutoSaveEnabled
|
|
}
|
|
}
|