Feedback iPad: allocation grande + %, Home 2 columnas, YoY KPIs, achievements fix, iCloud calmado (build 58)
Allocation: - Semicírculo responsive que llena su mitad de la fila (grande en iPad, no minúsculo en una esquina) + porcentaje dentro de cada segmento >=7%. SemicircleArc acepta center explícito; labels en el ángulo medio del arco. Home iPad: - Ya no columna única estrecha a 720. Hero + check-in a ancho completo, resto de cards en DOS columnas masonry independientes (sin acoplar alturas → sin gaps del grid). Cap 1100pt. Year vs Year KPIs: - Retorno del año actual + diferencia (pp) vs año anterior + mejor/peor año histórico. Usa forecastEndValue para el año en curso. Fixes: - 'View all achievements' en Journal no hacía nada: NavigationLink inline (frágil según contexto de navegación) → sheet fiable en cualquier sitio. - iCloud: CKErrorPartialFailure tras una sync que funciona ya NO se pinta en rojo alarmante — nota calmada 'Sincronizando con iCloud' + detalle técnico en disclosure. Rojo solo si nunca ha sincronizado y es fallo total. syncErrorIsCritical + strings ×7. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
This commit is contained in:
@@ -171,18 +171,49 @@ struct DashboardView: View {
|
||||
|
||||
// MARK: - iPad Layout Helpers
|
||||
|
||||
/// Full-width "hero" zones that lead the dashboard, in order.
|
||||
private var iPadLeadSections: [DashboardSectionConfig] {
|
||||
let leadIds = [DashboardSection.totalValue.id, DashboardSection.monthlyCheckIn.id]
|
||||
return leadIds.compactMap { id in visibleSections.first { $0.id == id } }
|
||||
}
|
||||
|
||||
/// Remaining context cards, split into two balanced masonry columns so the
|
||||
/// dashboard fills the iPad width without the row-height coupling (and gaps)
|
||||
/// a LazyVGrid produced.
|
||||
private var iPadContextColumns: ([DashboardSectionConfig], [DashboardSectionConfig]) {
|
||||
let leadIds = Set([DashboardSection.totalValue.id, DashboardSection.monthlyCheckIn.id])
|
||||
let rest = visibleSections.filter { !leadIds.contains($0.id) }
|
||||
var left: [DashboardSectionConfig] = []
|
||||
var right: [DashboardSectionConfig] = []
|
||||
for (i, cfg) in rest.enumerated() {
|
||||
if i.isMultiple(of: 2) { left.append(cfg) } else { right.append(cfg) }
|
||||
}
|
||||
return (left, right)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var iPadDashboardLayout: some View {
|
||||
let columns = iPadContextColumns
|
||||
VStack(spacing: 16) {
|
||||
ForEach(visibleSections) { config in
|
||||
ForEach(iPadLeadSections) { config in
|
||||
sectionView(for: config)
|
||||
if config.id == DashboardSection.monthlyCheckIn.id,
|
||||
!viewModel.insights.isEmpty {
|
||||
if config.id == DashboardSection.monthlyCheckIn.id, !viewModel.insights.isEmpty {
|
||||
InsightsRow(insights: viewModel.insights)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(alignment: .top, spacing: 16) {
|
||||
VStack(spacing: 16) {
|
||||
ForEach(columns.0) { sectionView(for: $0) }
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
VStack(spacing: 16) {
|
||||
ForEach(columns.1) { sectionView(for: $0) }
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: 720)
|
||||
.frame(maxWidth: 1100)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
@@ -776,6 +807,7 @@ struct MomentumStreaksCard: View {
|
||||
/// and achievement detail don't fit — stack tiles in a grid and drop detail.
|
||||
var compact = false
|
||||
@State private var stats: MonthlyCheckInStats = .empty
|
||||
@State private var showingAchievements = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
@@ -865,8 +897,11 @@ struct MomentumStreaksCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
AchievementsView(referenceDate: Date())
|
||||
// Sheet instead of NavigationLink: this card lives in several
|
||||
// navigation contexts (dashboard column, Journal sidebar) where an
|
||||
// inline push was unreliable — the tap did nothing on iPad.
|
||||
Button {
|
||||
showingAchievements = true
|
||||
} label: {
|
||||
HStack {
|
||||
Text(String(localized: "achievements_view_all"))
|
||||
@@ -877,6 +912,7 @@ struct MomentumStreaksCard: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
@@ -888,6 +924,16 @@ struct MomentumStreaksCard: View {
|
||||
.onReceive(NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange)) { _ in
|
||||
refreshStats()
|
||||
}
|
||||
.sheet(isPresented: $showingAchievements) {
|
||||
NavigationStack {
|
||||
AchievementsView(referenceDate: Date())
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(String(localized: "cancel")) { showingAchievements = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var onTimeRateText: String {
|
||||
|
||||
Reference in New Issue
Block a user