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:
@@ -5,7 +5,6 @@ import Charts
|
||||
struct SourceDetailView: View {
|
||||
@StateObject private var viewModel: SourceDetailViewModel
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@AppStorage("calmModeEnabled") private var calmModeEnabled = true
|
||||
|
||||
@State private var showingDeleteConfirmation = false
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
@@ -45,11 +44,7 @@ struct SourceDetailView: View {
|
||||
headerCard
|
||||
|
||||
// Performance Summary — full width, above quick actions
|
||||
if calmModeEnabled {
|
||||
simpleMetricsSection
|
||||
} else {
|
||||
metricsSection
|
||||
}
|
||||
metricsSection
|
||||
|
||||
// Quick Actions
|
||||
quickActions
|
||||
@@ -145,24 +140,13 @@ struct SourceDetailView: View {
|
||||
.font(.system(size: 36, weight: .bold, design: .rounded))
|
||||
|
||||
// Return
|
||||
if calmModeEnabled {
|
||||
VStack(spacing: 2) {
|
||||
Text("All-time return")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text("\(viewModel.formattedTotalReturn) (\(viewModel.formattedPercentageReturn))")
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
} else {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: viewModel.isPositiveReturn ? "arrow.up.right" : "arrow.down.right")
|
||||
Text(viewModel.formattedTotalReturn)
|
||||
Text("(\(viewModel.formattedPercentageReturn))")
|
||||
}
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(viewModel.isPositiveReturn ? .positiveGreen : .negativeRed)
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: viewModel.isPositiveReturn ? "arrow.up.right" : "arrow.down.right")
|
||||
Text(viewModel.formattedTotalReturn)
|
||||
Text("(\(viewModel.formattedPercentageReturn))")
|
||||
}
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundColor(viewModel.isPositiveReturn ? .positiveGreen : .negativeRed)
|
||||
|
||||
// Last updated
|
||||
Text("Last updated: \(viewModel.lastUpdated)")
|
||||
@@ -402,27 +386,6 @@ struct SourceDetailView: View {
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
|
||||
}
|
||||
|
||||
private var simpleMetricsSection: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Performance Summary")
|
||||
.font(.headline)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
MetricChip(title: "CAGR", value: viewModel.metrics.formattedCAGR)
|
||||
.frame(maxWidth: .infinity)
|
||||
MetricChip(title: "Avg Monthly", value: viewModel.metrics.formattedAverageMonthlyReturn)
|
||||
.frame(maxWidth: .infinity)
|
||||
MetricChip(title: "Contributions", value: viewModel.source.totalContributions.compactCurrencyString)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(AppConstants.UI.cornerRadius)
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
|
||||
}
|
||||
|
||||
// MARK: - Snapshots Section
|
||||
|
||||
private var snapshotsSection: some View {
|
||||
|
||||
Reference in New Issue
Block a user