1.6.0 #10 ocultar saldos + Face ID · #11 widget interactivo + Siri

#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:
alexandrev-tibco
2026-07-25 17:52:09 +02:00
parent 648c2bd805
commit 326be6db04
20 changed files with 587 additions and 8 deletions
+29 -3
View File
@@ -1,6 +1,7 @@
import WidgetKit
import SwiftUI
import CoreData
import AppIntents
private let appGroupIdentifier = "group.com.alexandrevazquez.portfoliojournal"
private let storeFileName = "PortfolioJournal.sqlite"
@@ -753,9 +754,13 @@ struct MediumWidgetView: View {
HStack(spacing: 16) {
// Left side - Total value + engagement metrics
VStack(alignment: .leading, spacing: 8) {
Text("Portfolio")
.font(.caption)
.foregroundColor(.secondary)
HStack(spacing: 6) {
Text("Portfolio")
.font(.caption)
.foregroundColor(.secondary)
Spacer(minLength: 0)
WidgetRefreshButton()
}
Text(entry.totalValue.compactCurrencyString(currencyCode: entry.currencyCode))
.font(.title.weight(.bold))
@@ -884,6 +889,7 @@ struct LargeWidgetView: View {
VStack(alignment: .trailing, spacing: 6) {
if entry.streak >= 1 { StreakPill(streak: entry.streak) }
CheckInPill(date: entry.nextCheckInDate)
WidgetRefreshButton()
}
}
@@ -1223,6 +1229,26 @@ struct CombinedCategoryChartView: View {
}
}
// MARK: - Interactive Refresh Button (iOS 17+)
/// A small refresh control that reloads the widget timeline in-process via an
/// AppIntent. Only rendered on iOS 17+, where `Button(intent:)` is available;
/// on iOS 16 it collapses to nothing so the widget still builds and works with
/// the existing deep links.
struct WidgetRefreshButton: View {
var body: some View {
if #available(iOS 17.0, *) {
Button(intent: RefreshWidgetIntent()) {
Image(systemName: "arrow.clockwise")
.font(.caption2.weight(.semibold))
.foregroundColor(.secondary)
}
.buttonStyle(.plain)
.invalidatableContent()
}
}
}
// MARK: - Widget Configuration
struct InvestmentWidget: Widget {