keys-ios-integration/Sources/KeysForAll/Core/KeysForAllManager.swift
2025-07-22 01:42:57 -07:00

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
}
}
}