#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
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user