326be6db04
#10 Ocultar saldos: BalancePrivacyManager (balancesHidden + requireBiometricToReveal), modifier .hiddenBalance() (blur + máscara de bullets, ancho estable), botón ojo en Dashboard, revelar con Face ID vía AppLockService, sección Privacidad en Settings. Aplicado a total portfolio, forecast, sources, goals, monthly summary. #11 Siri/App Intents: NetWorthIntent ('¿cuál es mi patrimonio?' → 'Tu patrimonio es X'), StreakIntent, 5 AppShortcuts con frases. Widget interactivo iOS 17: botón Refresh (Button(intent:) → reloadAllTimelines, in-process) en medium/large, gated #available(iOS 17). Reader read-only del AppGroup. Dialogs Siri refactorizados a String(localized:). Localización: strings de #10 y de Siri en los 7 idiomas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
67 lines
2.5 KiB
Swift
67 lines
2.5 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|