import SwiftUI import Combine /// Central store for the "Hide balances" privacy feature. /// /// When `balancesHidden` is true, every monetary amount in the app renders /// redacted (a blurred bullet mask) instead of the real value. This lets the /// user open the app in public or take screenshots without exposing net worth. /// /// If `requireBiometricToReveal` is enabled, un-hiding balances must first pass /// Face ID / biometric auth (reusing `AppLockService`). On failure, balances /// stay hidden. /// /// State is persisted in `UserDefaults` so it survives relaunches, and mirrored /// into `@Published` properties so SwiftUI views observing this object update /// instantly when the user taps the quick toggle. @MainActor final class BalancePrivacyManager: ObservableObject { static let shared = BalancePrivacyManager() private enum Keys { static let balancesHidden = "balancesHidden" static let requireBiometricToReveal = "requireBiometricToReveal" } /// When true, all monetary amounts render redacted. @Published var balancesHidden: Bool { didSet { UserDefaults.standard.set(balancesHidden, forKey: Keys.balancesHidden) } } /// When true, revealing hidden balances requires biometric auth. @Published var requireBiometricToReveal: Bool { didSet { UserDefaults.standard.set(requireBiometricToReveal, forKey: Keys.requireBiometricToReveal) } } init() { self.balancesHidden = UserDefaults.standard.bool(forKey: Keys.balancesHidden) self.requireBiometricToReveal = UserDefaults.standard.bool(forKey: Keys.requireBiometricToReveal) } /// Toggle balance visibility. Hiding is always immediate. Revealing, when /// `requireBiometricToReveal` is on and biometrics are available, runs a /// Face ID prompt first and only reveals on success. func toggleHidden() { if balancesHidden { reveal() } else { balancesHidden = true } } /// Attempt to reveal balances, honoring the biometric requirement. func reveal() { guard balancesHidden else { return } if requireBiometricToReveal && AppLockService.canUseBiometrics() { AppLockService.authenticate(reason: "Reveal your balances") { [weak self] success in if success { self?.balancesHidden = false } // On failure, stay hidden — do nothing. } } else { balancesHidden = false } } }