From b5a5c62fa87847c5e7c28a05d69f693ca590c28c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 4 Jan 2026 13:22:57 -0600 Subject: [PATCH] Add PremiumGate utility for generic freemium settings gating - Add PremiumGate enum with get/canSet helpers for premium-gated settings - Supports entirely premium settings (returns default for free users) - Supports partial premium (only specific values require premium) - Preserves stored values so re-subscribing restores previous choices --- Sources/Bedrock/Premium/PremiumGate.swift | 108 ++++++++++++++++++ .../Bedrock/Resources/Localizable.xcstrings | 11 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 Sources/Bedrock/Premium/PremiumGate.swift diff --git a/Sources/Bedrock/Premium/PremiumGate.swift b/Sources/Bedrock/Premium/PremiumGate.swift new file mode 100644 index 0000000..ce0e62e --- /dev/null +++ b/Sources/Bedrock/Premium/PremiumGate.swift @@ -0,0 +1,108 @@ +// +// PremiumGate.swift +// Bedrock +// +// Utility for premium-gating settings values in freemium apps. +// Provides a consistent pattern for handling premium vs free user access. +// + +import Foundation + +/// Utility enum for premium-gating settings values. +/// +/// Use this to create consistent premium/free behavior across your app's settings. +/// Values are preserved in storage but defaults are returned for non-premium users, +/// so users don't lose their settings if they unsubscribe and later re-subscribe. +/// +/// ## Usage +/// +/// For boolean settings that are entirely premium: +/// ```swift +/// var isSkinSmoothingEnabled: Bool { +/// get { PremiumGate.get(cloudSync.data.isSkinSmoothingEnabled, default: false, isPremium: isPremiumUnlocked) } +/// set { guard PremiumGate.canSet(isPremium: isPremiumUnlocked) else { return } +/// updateSettings { $0.isSkinSmoothingEnabled = newValue } +/// } +/// } +/// ``` +/// +/// For settings where only some values are premium: +/// ```swift +/// var selectedTimer: TimerOption { +/// get { +/// let stored = TimerOption(rawValue: cloudSync.data.selectedTimerRaw) ?? .three +/// return PremiumGate.get(stored, default: .three, premiumValues: [.five, .ten], isPremium: isPremiumUnlocked) +/// } +/// set { +/// guard PremiumGate.canSet(newValue, premiumValues: [.five, .ten], isPremium: isPremiumUnlocked) else { return } +/// updateSettings { $0.selectedTimerRaw = newValue.rawValue } +/// } +/// } +/// ``` +public enum PremiumGate { + + // MARK: - Getters + + /// Returns the stored value if premium, otherwise returns the default. + /// + /// Use for settings that are entirely premium-only (e.g., skin smoothing, mirror flip). + /// - Parameters: + /// - stored: The value stored in settings + /// - defaultValue: The value to return for non-premium users + /// - isPremium: Whether the user has premium access + /// - Returns: The stored value if premium, otherwise the default + public static func get( + _ stored: T, + default defaultValue: T, + isPremium: Bool + ) -> T { + isPremium ? stored : defaultValue + } + + /// Returns the stored value if premium or if it's not a premium value, otherwise returns the default. + /// + /// Use for settings where only some values are premium (e.g., timer options, quality levels). + /// - Parameters: + /// - stored: The value stored in settings + /// - defaultValue: The value to return for non-premium users when they have a premium value stored + /// - premiumValues: The set of values that require premium access + /// - isPremium: Whether the user has premium access + /// - Returns: The stored value if premium or not a premium value, otherwise the default + public static func get( + _ stored: T, + default defaultValue: T, + premiumValues: Set, + isPremium: Bool + ) -> T { + if !isPremium && premiumValues.contains(stored) { + return defaultValue + } + return stored + } + + // MARK: - Setters + + /// Checks if setting any value should be allowed (for entirely premium settings). + /// + /// - Parameter isPremium: Whether the user has premium access + /// - Returns: True if the user can modify this setting + public static func canSet(isPremium: Bool) -> Bool { + isPremium + } + + /// Checks if setting a specific value should be allowed. + /// + /// Use for settings where only some values are premium. + /// - Parameters: + /// - value: The value the user wants to set + /// - premiumValues: The set of values that require premium access + /// - isPremium: Whether the user has premium access + /// - Returns: True if the user can set this specific value + public static func canSet( + _ value: T, + premiumValues: Set, + isPremium: Bool + ) -> Bool { + isPremium || !premiumValues.contains(value) + } +} diff --git a/Sources/Bedrock/Resources/Localizable.xcstrings b/Sources/Bedrock/Resources/Localizable.xcstrings index 3d7b7a9..09795b3 100644 --- a/Sources/Bedrock/Resources/Localizable.xcstrings +++ b/Sources/Bedrock/Resources/Localizable.xcstrings @@ -1 +1,10 @@ -{"sourceLanguage":"en","strings":{},"version":"1.0"} +{ + "sourceLanguage" : "en", + "strings" : { + "%lld%%" : { + "comment" : "A text label showing the current volume percentage. The argument is the volume as a percentage (0.0 to 1.0).", + "isCommentAutoGenerated" : true + } + }, + "version" : "1.1" +} \ No newline at end of file