Calm 2.0 Fase 1: sistema de diseño limpio + Home en 3 zonas (1.5.0 build 51)
Sistema: - AppBackground: fuera gradiente + formas decorativas → systemGroupedBackground plano en toda la app. La profundidad la da la jerarquía, no la decoración. Home (3 zonas: estado / acción / contexto): - TotalValueCard: hero tipográfico — número 44pt rounded sobre card limpia, chip de delta semántico (verde/rojo), sparkline de evolución 56pt estilo Stocks, fila secundaria YoY / inception / forecast. Fuera el gradiente azul. - MonthlyCheckInCard state-driven: pendiente → protagonista con progreso real de fuentes (N de M) y un CTA; completado → fila discreta con checkmark, próximo check-in y streak integrado. En iPad va a ancho completo bajo el hero (zona de acción), fuera del grid. - InsightsRow: de carrusel de chips a un insight único rotatorio (tap para ciclar, dots de posición). - Eliminados del top: streak badge (vive en el check-in) y banner de pending updates (el progreso del check-in lo comunica). - Defaults nuevos: momentumStreaks/pendingUpdates/periodReturns/monthlySummary ocultos por defecto (siguen disponibles en Customize; configs guardadas de usuarios existentes se respetan). - Strings nuevas en 7 idiomas (checkin_done_*, checkin_sources_progress). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
This commit is contained in:
@@ -1,49 +1,60 @@
|
||||
import SwiftUI
|
||||
|
||||
// Calm 2.0: one insight at a time instead of a chip carousel — a quiet,
|
||||
// glanceable line under the check-in zone. Tap to cycle through the rest.
|
||||
struct InsightsRow: View {
|
||||
let insights: [PortfolioInsight]
|
||||
@State private var index = 0
|
||||
|
||||
private var current: PortfolioInsight? {
|
||||
guard !insights.isEmpty else { return nil }
|
||||
return insights[index % insights.count]
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text(String(localized: "insights_section_title"))
|
||||
.font(.headline)
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
if let insight = current {
|
||||
Button {
|
||||
withAnimation(.snappy) {
|
||||
index = (index + 1) % insights.count
|
||||
}
|
||||
} label: {
|
||||
HStack(spacing: 12) {
|
||||
ForEach(insights) { insight in
|
||||
insightChip(insight)
|
||||
Image(systemName: insight.systemImage)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundColor(insight.accentColor)
|
||||
.frame(width: 30, height: 30)
|
||||
.background(insight.accentColor.opacity(0.12))
|
||||
.clipShape(Circle())
|
||||
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(insight.title)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text(insight.value)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if insights.count > 1 {
|
||||
HStack(spacing: 4) {
|
||||
ForEach(insights.indices, id: \.self) { i in
|
||||
Circle()
|
||||
.fill(i == index % insights.count ? Color.secondary : Color.secondary.opacity(0.25))
|
||||
.frame(width: 5, height: 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(14)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.cornerRadius(AppConstants.UI.cornerRadius)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.id(insight.id)
|
||||
.transition(.opacity)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(AppConstants.UI.cornerRadius)
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
|
||||
}
|
||||
|
||||
private func insightChip(_ insight: PortfolioInsight) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 5) {
|
||||
Image(systemName: insight.systemImage)
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundColor(insight.accentColor)
|
||||
Text(insight.title)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Text(insight.value)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 10)
|
||||
.background(insight.accentColor.opacity(0.08))
|
||||
.cornerRadius(12)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(insight.accentColor.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user