From 53170a1de457eddbf474acdd2cf344d3d34679c4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 4 Jan 2026 13:23:09 -0600 Subject: [PATCH] Refactor premium settings to use Bedrock PremiumGate utility - Replace inline premium checks with PremiumGate.get/canSet - Define premium values as static sets for timer options, colors, quality - Cleaner, more consistent premium gating pattern - Values preserved when user unsubscribes (restored on re-subscribe) --- .../Features/Settings/SettingsViewModel.swift | 69 ++++++++----------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/SelfieCam/Features/Settings/SettingsViewModel.swift b/SelfieCam/Features/Settings/SettingsViewModel.swift index 9620edf..028a939 100644 --- a/SelfieCam/Features/Settings/SettingsViewModel.swift +++ b/SelfieCam/Features/Settings/SettingsViewModel.swift @@ -133,15 +133,10 @@ final class SettingsViewModel: RingLightConfigurable { var lightColorId: String { get { let storedId = _cachedLightColorId ?? cloudSync.data.lightColorId - // Return default free color if not premium and stored color is premium - if !isPremiumUnlocked && Self.premiumColorIds.contains(storedId) { - return Self.defaultFreeColorId - } - return storedId + return PremiumGate.get(storedId, default: Self.defaultFreeColorId, premiumValues: Self.premiumColorIds, isPremium: isPremiumUnlocked) } set { - // Block premium color selection for free users - guard isPremiumUnlocked || !Self.premiumColorIds.contains(newValue) else { return } + guard PremiumGate.canSet(newValue, premiumValues: Self.premiumColorIds, isPremium: isPremiumUnlocked) else { return } _cachedLightColorId = newValue updateSettings { $0.lightColorId = newValue } } @@ -161,8 +156,7 @@ final class SettingsViewModel: RingLightConfigurable { ) } set { - // Block custom color changes for non-premium users - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } _cachedCustomColor = newValue let rgb = CustomColorRGB(from: newValue) debouncedSave(key: "customColor") { @@ -184,18 +178,18 @@ final class SettingsViewModel: RingLightConfigurable { /// Whether the camera preview is flipped to show a true mirror (PREMIUM) var isMirrorFlipped: Bool { - get { isPremiumUnlocked ? cloudSync.data.isMirrorFlipped : false } + get { PremiumGate.get(cloudSync.data.isMirrorFlipped, default: false, isPremium: isPremiumUnlocked) } set { - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } updateSettings { $0.isMirrorFlipped = newValue } } } /// Whether skin smoothing filter is enabled (PREMIUM) var isSkinSmoothingEnabled: Bool { - get { isPremiumUnlocked ? cloudSync.data.isSkinSmoothingEnabled : false } + get { PremiumGate.get(cloudSync.data.isSkinSmoothingEnabled, default: false, isPremium: isPremiumUnlocked) } set { - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } updateSettings { $0.isSkinSmoothingEnabled = newValue } } } @@ -223,21 +217,17 @@ final class SettingsViewModel: RingLightConfigurable { /// Convenience property for border width (same as ringSize) var borderWidth: CGFloat { ringSize } + /// Premium timer options that require subscription + private static let premiumTimerOptions: Set = [.five, .ten] + /// Selected timer option (5s and 10s are PREMIUM) var selectedTimer: TimerOption { get { let stored = TimerOption(rawValue: cloudSync.data.selectedTimerRaw) ?? .off - // Free users limited to off or 3s - if !isPremiumUnlocked && (stored == .five || stored == .ten) { - return .three - } - return stored + return PremiumGate.get(stored, default: .three, premiumValues: Self.premiumTimerOptions, isPremium: isPremiumUnlocked) } set { - // Block premium timer options for free users - if !isPremiumUnlocked && (newValue == .five || newValue == .ten) { - return - } + guard PremiumGate.canSet(newValue, premiumValues: Self.premiumTimerOptions, isPremium: isPremiumUnlocked) else { return } updateSettings { $0.selectedTimerRaw = newValue.rawValue } } } @@ -256,37 +246,36 @@ final class SettingsViewModel: RingLightConfigurable { /// Whether flash is synced with ring light color (PREMIUM) var isFlashSyncedWithRingLight: Bool { - get { isPremiumUnlocked ? cloudSync.data.isFlashSyncedWithRingLight : false } + get { PremiumGate.get(cloudSync.data.isFlashSyncedWithRingLight, default: false, isPremium: isPremiumUnlocked) } set { - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } updateSettings { $0.isFlashSyncedWithRingLight = newValue } } } /// HDR mode setting (PREMIUM) var hdrMode: CameraHDRMode { - get { isPremiumUnlocked ? (CameraHDRMode(rawValue: cloudSync.data.hdrModeRaw) ?? .off) : .off } + get { + let stored = CameraHDRMode(rawValue: cloudSync.data.hdrModeRaw) ?? .off + return PremiumGate.get(stored, default: .off, isPremium: isPremiumUnlocked) + } set { - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } updateSettings { $0.hdrModeRaw = newValue.rawValue } } } + /// Premium photo quality options that require subscription + private static let premiumPhotoQualities: Set = [.high] + /// Photo quality setting (high is PREMIUM) var photoQuality: PhotoQuality { get { let stored = PhotoQuality(rawValue: cloudSync.data.photoQualityRaw) ?? PhotoQuality.high - // Free users limited to medium quality - if !isPremiumUnlocked && stored == PhotoQuality.high { - return PhotoQuality.medium - } - return stored + return PremiumGate.get(stored, default: .medium, premiumValues: Self.premiumPhotoQualities, isPremium: isPremiumUnlocked) } set { - // Block premium quality option for free users - if !isPremiumUnlocked && newValue == PhotoQuality.high { - return - } + guard PremiumGate.canSet(newValue, premiumValues: Self.premiumPhotoQualities, isPremium: isPremiumUnlocked) else { return } updateSettings { $0.photoQualityRaw = newValue.rawValue } } } @@ -316,9 +305,9 @@ final class SettingsViewModel: RingLightConfigurable { /// Whether Center Stage is enabled (PREMIUM) var isCenterStageEnabled: Bool { - get { isPremiumUnlocked ? cloudSync.data.isCenterStageEnabled : false } + get { PremiumGate.get(cloudSync.data.isCenterStageEnabled, default: false, isPremium: isPremiumUnlocked) } set { - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } updateSettings { $0.isCenterStageEnabled = newValue } } } @@ -348,7 +337,7 @@ final class SettingsViewModel: RingLightConfigurable { /// Sets the custom color and selects it (PREMIUM) func selectCustomColor(_ color: Color) { - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } customColor = color lightColorId = RingLightColor.customId } @@ -360,9 +349,9 @@ final class SettingsViewModel: RingLightConfigurable { /// Whether iCloud sync is enabled (PREMIUM) var iCloudEnabled: Bool { - get { isPremiumUnlocked ? cloudSync.iCloudEnabled : false } + get { PremiumGate.get(cloudSync.iCloudEnabled, default: false, isPremium: isPremiumUnlocked) } set { - guard isPremiumUnlocked else { return } + guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } cloudSync.iCloudEnabled = newValue } }