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 {
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<TimerOption> = [.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<PhotoQuality> = [.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
}
}