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)
This commit is contained in:
Matt Bruce 2026-01-04 13:23:09 -06:00
parent 459755be98
commit 53170a1de4

View File

@ -133,15 +133,10 @@ final class SettingsViewModel: RingLightConfigurable {
var lightColorId: String { var lightColorId: String {
get { get {
let storedId = _cachedLightColorId ?? cloudSync.data.lightColorId let storedId = _cachedLightColorId ?? cloudSync.data.lightColorId
// Return default free color if not premium and stored color is premium return PremiumGate.get(storedId, default: Self.defaultFreeColorId, premiumValues: Self.premiumColorIds, isPremium: isPremiumUnlocked)
if !isPremiumUnlocked && Self.premiumColorIds.contains(storedId) {
return Self.defaultFreeColorId
}
return storedId
} }
set { set {
// Block premium color selection for free users guard PremiumGate.canSet(newValue, premiumValues: Self.premiumColorIds, isPremium: isPremiumUnlocked) else { return }
guard isPremiumUnlocked || !Self.premiumColorIds.contains(newValue) else { return }
_cachedLightColorId = newValue _cachedLightColorId = newValue
updateSettings { $0.lightColorId = newValue } updateSettings { $0.lightColorId = newValue }
} }
@ -161,8 +156,7 @@ final class SettingsViewModel: RingLightConfigurable {
) )
} }
set { set {
// Block custom color changes for non-premium users guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
guard isPremiumUnlocked else { return }
_cachedCustomColor = newValue _cachedCustomColor = newValue
let rgb = CustomColorRGB(from: newValue) let rgb = CustomColorRGB(from: newValue)
debouncedSave(key: "customColor") { debouncedSave(key: "customColor") {
@ -184,18 +178,18 @@ final class SettingsViewModel: RingLightConfigurable {
/// Whether the camera preview is flipped to show a true mirror (PREMIUM) /// Whether the camera preview is flipped to show a true mirror (PREMIUM)
var isMirrorFlipped: Bool { var isMirrorFlipped: Bool {
get { isPremiumUnlocked ? cloudSync.data.isMirrorFlipped : false } get { PremiumGate.get(cloudSync.data.isMirrorFlipped, default: false, isPremium: isPremiumUnlocked) }
set { set {
guard isPremiumUnlocked else { return } guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
updateSettings { $0.isMirrorFlipped = newValue } updateSettings { $0.isMirrorFlipped = newValue }
} }
} }
/// Whether skin smoothing filter is enabled (PREMIUM) /// Whether skin smoothing filter is enabled (PREMIUM)
var isSkinSmoothingEnabled: Bool { var isSkinSmoothingEnabled: Bool {
get { isPremiumUnlocked ? cloudSync.data.isSkinSmoothingEnabled : false } get { PremiumGate.get(cloudSync.data.isSkinSmoothingEnabled, default: false, isPremium: isPremiumUnlocked) }
set { set {
guard isPremiumUnlocked else { return } guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
updateSettings { $0.isSkinSmoothingEnabled = newValue } updateSettings { $0.isSkinSmoothingEnabled = newValue }
} }
} }
@ -223,21 +217,17 @@ final class SettingsViewModel: RingLightConfigurable {
/// Convenience property for border width (same as ringSize) /// Convenience property for border width (same as ringSize)
var borderWidth: CGFloat { ringSize } var borderWidth: CGFloat { ringSize }
/// Premium timer options that require subscription
private static let premiumTimerOptions: Set<TimerOption> = [.five, .ten]
/// Selected timer option (5s and 10s are PREMIUM) /// Selected timer option (5s and 10s are PREMIUM)
var selectedTimer: TimerOption { var selectedTimer: TimerOption {
get { get {
let stored = TimerOption(rawValue: cloudSync.data.selectedTimerRaw) ?? .off let stored = TimerOption(rawValue: cloudSync.data.selectedTimerRaw) ?? .off
// Free users limited to off or 3s return PremiumGate.get(stored, default: .three, premiumValues: Self.premiumTimerOptions, isPremium: isPremiumUnlocked)
if !isPremiumUnlocked && (stored == .five || stored == .ten) {
return .three
}
return stored
} }
set { set {
// Block premium timer options for free users guard PremiumGate.canSet(newValue, premiumValues: Self.premiumTimerOptions, isPremium: isPremiumUnlocked) else { return }
if !isPremiumUnlocked && (newValue == .five || newValue == .ten) {
return
}
updateSettings { $0.selectedTimerRaw = newValue.rawValue } updateSettings { $0.selectedTimerRaw = newValue.rawValue }
} }
} }
@ -256,37 +246,36 @@ final class SettingsViewModel: RingLightConfigurable {
/// Whether flash is synced with ring light color (PREMIUM) /// Whether flash is synced with ring light color (PREMIUM)
var isFlashSyncedWithRingLight: Bool { var isFlashSyncedWithRingLight: Bool {
get { isPremiumUnlocked ? cloudSync.data.isFlashSyncedWithRingLight : false } get { PremiumGate.get(cloudSync.data.isFlashSyncedWithRingLight, default: false, isPremium: isPremiumUnlocked) }
set { set {
guard isPremiumUnlocked else { return } guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
updateSettings { $0.isFlashSyncedWithRingLight = newValue } updateSettings { $0.isFlashSyncedWithRingLight = newValue }
} }
} }
/// HDR mode setting (PREMIUM) /// HDR mode setting (PREMIUM)
var hdrMode: CameraHDRMode { 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 { set {
guard isPremiumUnlocked else { return } guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
updateSettings { $0.hdrModeRaw = newValue.rawValue } updateSettings { $0.hdrModeRaw = newValue.rawValue }
} }
} }
/// Premium photo quality options that require subscription
private static let premiumPhotoQualities: Set<PhotoQuality> = [.high]
/// Photo quality setting (high is PREMIUM) /// Photo quality setting (high is PREMIUM)
var photoQuality: PhotoQuality { var photoQuality: PhotoQuality {
get { get {
let stored = PhotoQuality(rawValue: cloudSync.data.photoQualityRaw) ?? PhotoQuality.high let stored = PhotoQuality(rawValue: cloudSync.data.photoQualityRaw) ?? PhotoQuality.high
// Free users limited to medium quality return PremiumGate.get(stored, default: .medium, premiumValues: Self.premiumPhotoQualities, isPremium: isPremiumUnlocked)
if !isPremiumUnlocked && stored == PhotoQuality.high {
return PhotoQuality.medium
}
return stored
} }
set { set {
// Block premium quality option for free users guard PremiumGate.canSet(newValue, premiumValues: Self.premiumPhotoQualities, isPremium: isPremiumUnlocked) else { return }
if !isPremiumUnlocked && newValue == PhotoQuality.high {
return
}
updateSettings { $0.photoQualityRaw = newValue.rawValue } updateSettings { $0.photoQualityRaw = newValue.rawValue }
} }
} }
@ -316,9 +305,9 @@ final class SettingsViewModel: RingLightConfigurable {
/// Whether Center Stage is enabled (PREMIUM) /// Whether Center Stage is enabled (PREMIUM)
var isCenterStageEnabled: Bool { var isCenterStageEnabled: Bool {
get { isPremiumUnlocked ? cloudSync.data.isCenterStageEnabled : false } get { PremiumGate.get(cloudSync.data.isCenterStageEnabled, default: false, isPremium: isPremiumUnlocked) }
set { set {
guard isPremiumUnlocked else { return } guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
updateSettings { $0.isCenterStageEnabled = newValue } updateSettings { $0.isCenterStageEnabled = newValue }
} }
} }
@ -348,7 +337,7 @@ final class SettingsViewModel: RingLightConfigurable {
/// Sets the custom color and selects it (PREMIUM) /// Sets the custom color and selects it (PREMIUM)
func selectCustomColor(_ color: Color) { func selectCustomColor(_ color: Color) {
guard isPremiumUnlocked else { return } guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
customColor = color customColor = color
lightColorId = RingLightColor.customId lightColorId = RingLightColor.customId
} }
@ -360,9 +349,9 @@ final class SettingsViewModel: RingLightConfigurable {
/// Whether iCloud sync is enabled (PREMIUM) /// Whether iCloud sync is enabled (PREMIUM)
var iCloudEnabled: Bool { var iCloudEnabled: Bool {
get { isPremiumUnlocked ? cloudSync.iCloudEnabled : false } get { PremiumGate.get(cloudSync.iCloudEnabled, default: false, isPremium: isPremiumUnlocked) }
set { set {
guard isPremiumUnlocked else { return } guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return }
cloudSync.iCloudEnabled = newValue cloudSync.iCloudEnabled = newValue
} }
} }