Rediseño Charts iPad, zoom en gráficas, eliminación de Focus Mode y fixes de rendimiento
Charts iPad (rediseño): - Fila de KPIs sobre el chart: Total Value, Period Return, CAGR, Volatility y Max Drawdown del rango activo (nuevo ChartsViewModel.portfolioMetrics, calculado sobre los TOTALES MENSUALES AGREGADOS — pasar snapshots multi-source crudos a calculateMetrics producía KPIs absurdos) - Sidebar (248pt) con tiles de chart type en grid 2 col agrupados por sección: Overview / Analyze / Risk / Forecast, con candado premium y accesibilidad - Selector de periodo como Picker segmentado nativo en la cabecera del chart (fuera del sidebar); título + descripción del chart visibles - Eliminado sheet de paywall duplicado del layout iPad Zoom (todas las series temporales): - Nuevo ChartZoom.swift: pinch + botones +/- y reset (HIG: el gesto nunca es el único camino), chartScrollableAxes + chartXVisibleDomain al hacer zoom - Integrado en Evolution, Contributions, Rolling 12M, Cashflow, Drawdown y Volatility Focus Mode eliminado (todo siempre disponible): - Fuera el toggle de Dashboard/Charts/Settings/Onboarding y los 6 @AppStorage - Todos los chart types siempre visibles; variantes completas en SourceDetail/SourceList - Home: Total Portfolio Value muestra SIEMPRE el cambio desde el último check-in - PeriodReturnsCard siempre visible Rendimiento y bugs (de la auditoría): - El cache de snapshots ya no se invalida al cambiar solo de chart type/rango/breakdown - calculateAnnualizedGrowth y el forecast a 12 meses ahora componen (la aproximación lineal sobreestimaba con historiales cortos) - Drawdown sin force unwrap; simulador: rama muerta reemplazada por preservación real de las asignaciones simuladas del usuario - UITest de captura de Charts con manejo del alert de notificaciones 12 strings nuevas localizadas en los 7 idiomas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
This commit is contained in:
@@ -11,7 +11,6 @@ struct DashboardView: View {
|
||||
@State private var showingAddSource = false
|
||||
@State private var showingCustomize = false
|
||||
@State private var sectionConfigs = DashboardLayoutStore.load()
|
||||
@AppStorage("calmModeEnabled") private var calmModeEnabled = true
|
||||
@AppStorage("showForecast") private var showForecast = true
|
||||
@State private var pendingAlertDismissed = false
|
||||
@State private var fullScreenSection: DashboardSection? = nil
|
||||
@@ -103,9 +102,6 @@ struct DashboardView: View {
|
||||
Image(systemName: "slider.horizontal.3")
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
dashboardFocusModeButton
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.selectedAccount = accountStore.selectedAccount
|
||||
@@ -150,24 +146,6 @@ struct DashboardView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var dashboardFocusModeButton: some View {
|
||||
Button {
|
||||
calmModeEnabled.toggle()
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: calmModeEnabled ? "moon.stars.fill" : "moon.stars")
|
||||
Text(calmModeEnabled ? "Focus" : "Full")
|
||||
.font(.caption.weight(.semibold))
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
.background(calmModeEnabled ? Color.appPrimary.opacity(0.12) : Color.gray.opacity(0.1))
|
||||
.foregroundColor(calmModeEnabled ? .appPrimary : .secondary)
|
||||
.cornerRadius(14)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private var streakBadge: some View {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "flame.fill")
|
||||
@@ -354,13 +332,9 @@ struct DashboardView: View {
|
||||
} else {
|
||||
TotalValueCard(
|
||||
totalValue: viewModel.portfolioSummary.formattedTotalValue,
|
||||
changeText: calmModeEnabled
|
||||
? "\(viewModel.latestPortfolioChange.formattedAbsolute) (\(viewModel.latestPortfolioChange.formattedPercentage))"
|
||||
: viewModel.portfolioSummary.formattedDayChange,
|
||||
changeLabel: calmModeEnabled ? "since last check-in" : "today",
|
||||
isPositive: calmModeEnabled
|
||||
? viewModel.latestPortfolioChange.absolute >= 0
|
||||
: viewModel.isDayChangePositive,
|
||||
changeText: "\(viewModel.latestPortfolioChange.formattedAbsolute) (\(viewModel.latestPortfolioChange.formattedPercentage))",
|
||||
changeLabel: "since last check-in",
|
||||
isPositive: viewModel.latestPortfolioChange.absolute >= 0,
|
||||
forecast: showForecast ? viewModel.portfolioForecast : nil,
|
||||
isPremium: iapService.isPremium,
|
||||
onUnlockTap: {
|
||||
@@ -373,10 +347,8 @@ struct DashboardView: View {
|
||||
onShareTap: {
|
||||
ShareService.shared.sharePortfolioValue(
|
||||
totalValue: viewModel.portfolioSummary.formattedTotalValue,
|
||||
changeText: calmModeEnabled
|
||||
? "\(viewModel.latestPortfolioChange.formattedAbsolute) (\(viewModel.latestPortfolioChange.formattedPercentage))"
|
||||
: viewModel.portfolioSummary.formattedDayChange,
|
||||
changeLabel: calmModeEnabled ? "since last check-in" : "today",
|
||||
changeText: "\(viewModel.latestPortfolioChange.formattedAbsolute) (\(viewModel.latestPortfolioChange.formattedPercentage))",
|
||||
changeLabel: "since last check-in",
|
||||
yearChange: viewModel.portfolioSummary.formattedYearChange,
|
||||
sinceInceptionChange: viewModel.portfolioSummary.formattedAllTimeReturn
|
||||
)
|
||||
@@ -449,19 +421,17 @@ struct DashboardView: View {
|
||||
PendingUpdatesCard(sources: viewModel.sourcesNeedingUpdate)
|
||||
}
|
||||
case .periodReturns:
|
||||
if !calmModeEnabled {
|
||||
if config.isCollapsed {
|
||||
CompactCard(title: "Returns", subtitle: viewModel.portfolioSummary.formattedMonthChange)
|
||||
} else {
|
||||
PeriodReturnsCard(
|
||||
monthChange: viewModel.portfolioSummary.formattedMonthChange,
|
||||
yearChange: viewModel.portfolioSummary.formattedYearChange,
|
||||
allTimeChange: viewModel.portfolioSummary.formattedAllTimeReturn,
|
||||
isMonthPositive: viewModel.isMonthChangePositive,
|
||||
isYearPositive: viewModel.isYearChangePositive,
|
||||
isAllTimePositive: viewModel.portfolioSummary.allTimeReturn >= 0
|
||||
)
|
||||
}
|
||||
if config.isCollapsed {
|
||||
CompactCard(title: "Returns", subtitle: viewModel.portfolioSummary.formattedMonthChange)
|
||||
} else {
|
||||
PeriodReturnsCard(
|
||||
monthChange: viewModel.portfolioSummary.formattedMonthChange,
|
||||
yearChange: viewModel.portfolioSummary.formattedYearChange,
|
||||
allTimeChange: viewModel.portfolioSummary.formattedAllTimeReturn,
|
||||
isMonthPositive: viewModel.isMonthChangePositive,
|
||||
isYearPositive: viewModel.isYearChangePositive,
|
||||
isAllTimePositive: viewModel.portfolioSummary.allTimeReturn >= 0
|
||||
)
|
||||
}
|
||||
case .contributionsVsReturns:
|
||||
let contrib = viewModel.portfolioSummary.totalContributions
|
||||
|
||||
Reference in New Issue
Block a user