Home: carrusel de charts con gating premium (candado) + aviso de próximo check-in
- EvolutionChartCard: swipe entre 7 charts (evolution, allocation libres; performance, drawdown, volatility, prediction, year-over-year premium). No premium ve las premium con candado + CTA de unlock; premium las ve reales. Reutiliza ChartsViewModel (computa solo la página visible) y las vistas del feature Charts. - MonthlyCheckInCard: aviso de próximo check-in siempre visible (fecha + badge de días restantes/atrasado), con fallback cuando no hay check-in previo. - Localización de nuevas cadenas en los 7 idiomas. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZFmGhbWzibhApev3C4554
This commit is contained in:
@@ -309,7 +309,9 @@ struct DashboardView: View {
|
||||
goals: goalsViewModel.goals,
|
||||
allocation: viewModel.categoryMetrics.map {
|
||||
(category: $0.categoryName, value: $0.totalValue, color: $0.colorHex)
|
||||
}
|
||||
},
|
||||
iapService: iapService,
|
||||
onRequestUpgrade: { viewModel.showingPaywall = true }
|
||||
)
|
||||
}
|
||||
case .categoryBreakdown:
|
||||
@@ -572,9 +574,22 @@ struct MonthlyCheckInCard: View {
|
||||
}
|
||||
|
||||
private var nextCheckInDate: Date? {
|
||||
guard let last = effectiveLastCheckInDate else { return nil }
|
||||
let effective = MonthlyCheckInStore.effectiveMonth(for: last, relativeTo: last)
|
||||
return effective.adding(months: 1).endOfMonth
|
||||
if let last = effectiveLastCheckInDate {
|
||||
let effective = MonthlyCheckInStore.effectiveMonth(for: last, relativeTo: last)
|
||||
return effective.adding(months: 1).endOfMonth
|
||||
}
|
||||
// No prior check-in yet — still surface a due date (end of the current
|
||||
// effective month) so the reminder is always visible on the Home card.
|
||||
let effectiveNow = MonthlyCheckInStore.effectiveMonth(for: Date(), relativeTo: Date())
|
||||
return effectiveNow.endOfMonth
|
||||
}
|
||||
|
||||
/// Short urgency badge shown next to the next check-in date.
|
||||
private var deadlineBadgeText: String {
|
||||
guard let days = daysUntilDeadline else { return "" }
|
||||
if days < 0 { return String(localized: "checkin_overdue") }
|
||||
if days == 0 { return String(localized: "checkin_due_today") }
|
||||
return String(format: String(localized: "checkin_days_left"), days)
|
||||
}
|
||||
|
||||
private var isOverdue: Bool {
|
||||
@@ -785,9 +800,28 @@ struct MonthlyCheckInCard: View {
|
||||
}
|
||||
|
||||
if let nextDate = nextCheckInDate {
|
||||
Text("Next check-in: \(nextDate.mediumDateString)")
|
||||
.font(.caption)
|
||||
.foregroundColor(isOverdue ? .red : .secondary)
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "calendar")
|
||||
.font(.caption)
|
||||
.foregroundColor(isOverdue ? .red : .secondary)
|
||||
Text(String(format: String(localized: "checkin_next_due"), nextDate.mediumDateString))
|
||||
.font(.caption)
|
||||
.foregroundColor(isOverdue ? .red : .secondary)
|
||||
Spacer()
|
||||
if !deadlineBadgeText.isEmpty {
|
||||
Text(deadlineBadgeText)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundColor(progressBarTint)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 3)
|
||||
.background(progressBarTint.opacity(0.12))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color(.tertiarySystemFill))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
Reference in New Issue
Block a user