164 lines
No EOL
5.4 KiB
Swift
164 lines
No EOL
5.4 KiB
Swift
//
|
|
// KeysForAllManager.swift
|
|
// KeysForAll
|
|
//
|
|
// Main manager for the Keys for All system
|
|
//
|
|
|
|
import SwiftUI
|
|
import Combine
|
|
|
|
/// Main manager for Keys for All functionality
|
|
@MainActor
|
|
public class KeysForAllManager<Provider: FeatureProvider>: ObservableObject {
|
|
// MARK: - Properties
|
|
|
|
internal let featureProvider: Provider
|
|
private let storage: KeyStorage
|
|
|
|
@Published public private(set) var keyCount: Int = 0
|
|
@Published public private(set) var currentLevel: LicenseLevel = .free
|
|
@Published public private(set) var isKeyGatingEnabled: Bool = true
|
|
@Published public private(set) var unlockedFeatures: Set<Provider.Feature> = []
|
|
@Published public private(set) var lockedFeatures: Set<Provider.Feature> = []
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
// MARK: - Initialization
|
|
|
|
public init(featureProvider: Provider, storage: KeyStorage) {
|
|
self.featureProvider = featureProvider
|
|
self.storage = storage
|
|
|
|
// Load initial state
|
|
self.keyCount = storage.getKeyCount()
|
|
self.isKeyGatingEnabled = storage.isKeyGatingEnabled()
|
|
self.currentLevel = LicenseLevel(keyCount: keyCount)
|
|
|
|
// Update features
|
|
updateFeatures()
|
|
|
|
// Observe changes
|
|
setupObservers()
|
|
}
|
|
|
|
// MARK: - Public Methods
|
|
|
|
/// Check if a feature is available
|
|
public func isFeatureAvailable(_ feature: Provider.Feature) -> Bool {
|
|
return !isKeyGatingEnabled || unlockedFeatures.contains(feature)
|
|
}
|
|
|
|
/// Get all features for a category
|
|
public func features(in category: FeatureCategory) -> [Provider.Feature] {
|
|
return featureProvider.allFeatures.filter { feature in
|
|
featureProvider.category(for: feature) == category
|
|
}
|
|
}
|
|
|
|
/// Get unlocked features by category
|
|
public func unlockedFeatures(in category: FeatureCategory) -> [Provider.Feature] {
|
|
return unlockedFeatures.filter { feature in
|
|
featureProvider.category(for: feature) == category
|
|
}
|
|
}
|
|
|
|
/// Add keys to the user's account
|
|
public func addKeys(_ count: Int) {
|
|
let newCount = keyCount + count
|
|
storage.setKeyCount(newCount)
|
|
keyCount = newCount
|
|
currentLevel = LicenseLevel(keyCount: newCount)
|
|
updateFeatures()
|
|
}
|
|
|
|
/// Remove keys from the user's account
|
|
public func removeKeys(_ count: Int) {
|
|
let newCount = max(0, keyCount - count)
|
|
storage.setKeyCount(newCount)
|
|
keyCount = newCount
|
|
currentLevel = LicenseLevel(keyCount: newCount)
|
|
updateFeatures()
|
|
}
|
|
|
|
/// Set whether key gating is enabled
|
|
public func setKeyGatingEnabled(_ enabled: Bool) {
|
|
storage.setKeyGatingEnabled(enabled)
|
|
isKeyGatingEnabled = enabled
|
|
updateFeatures()
|
|
}
|
|
|
|
/// Get the number of keys required for a feature
|
|
public func keysRequired(for feature: Provider.Feature) -> Int {
|
|
return featureProvider.requiredLevel(for: feature).requiredKeys
|
|
}
|
|
|
|
/// Get the number of additional keys needed to unlock a feature
|
|
public func additionalKeysNeeded(for feature: Provider.Feature) -> Int {
|
|
let required = keysRequired(for: feature)
|
|
return max(0, required - keyCount)
|
|
}
|
|
|
|
/// Create a gated view that shows different content based on feature availability
|
|
@ViewBuilder
|
|
public func gatedView<Content: View, Locked: View>(
|
|
for feature: Provider.Feature,
|
|
@ViewBuilder content: () -> Content,
|
|
@ViewBuilder lockedContent: () -> Locked
|
|
) -> some View {
|
|
if isFeatureAvailable(feature) {
|
|
content()
|
|
} else {
|
|
lockedContent()
|
|
}
|
|
}
|
|
|
|
// MARK: - Private Methods
|
|
|
|
private func setupObservers() {
|
|
// Observe UserDefaults changes if using UserDefaultsKeyStorage
|
|
if storage is UserDefaultsKeyStorage {
|
|
NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)
|
|
.sink { [weak self] _ in
|
|
self?.reloadFromStorage()
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
}
|
|
|
|
private func reloadFromStorage() {
|
|
let newKeyCount = storage.getKeyCount()
|
|
let newKeyGatingEnabled = storage.isKeyGatingEnabled()
|
|
|
|
if newKeyCount != keyCount || newKeyGatingEnabled != isKeyGatingEnabled {
|
|
keyCount = newKeyCount
|
|
isKeyGatingEnabled = newKeyGatingEnabled
|
|
currentLevel = LicenseLevel(keyCount: keyCount)
|
|
updateFeatures()
|
|
}
|
|
}
|
|
|
|
private func updateFeatures() {
|
|
if !isKeyGatingEnabled {
|
|
// All features unlocked when gating is disabled
|
|
unlockedFeatures = Set(featureProvider.allFeatures)
|
|
lockedFeatures = []
|
|
} else {
|
|
// Filter features based on current level
|
|
var unlocked = Set<Provider.Feature>()
|
|
var locked = Set<Provider.Feature>()
|
|
|
|
for feature in featureProvider.allFeatures {
|
|
let requiredLevel = featureProvider.requiredLevel(for: feature)
|
|
if currentLevel.rawValue >= requiredLevel.rawValue {
|
|
unlocked.insert(feature)
|
|
} else {
|
|
locked.insert(feature)
|
|
}
|
|
}
|
|
|
|
unlockedFeatures = unlocked
|
|
lockedFeatures = locked
|
|
}
|
|
}
|
|
} |