1.6.0: auto-rellenar huecos (snapshots estimados) + widgets/lock screen enriquecidos

Auto-relleno (#5): SnapshotRepository.autoFillGap crea snapshots interpolados
linealmente entre los dos snapshots que bordean el hueco, marcados isEstimated.
deleteEstimatedSnapshots para borrado en bloque. Botón 'Autocompletar N meses'
(wand.and.stars) en DataGapsSheet + 'Añadir manualmente'. Idempotente. Strings 7 idiomas.

Widgets/Lock Screen (#8, vía agente): familias enriquecidas — systemSmall/Medium/Large
con net worth + cambio desde check-in + racha + próximo check-in + progreso de objetivo;
lock screen accessoryCircular (gauge objetivo/milestone), accessoryRectangular, accessoryInline.
Deep link quickupdate preservado. Solo InvestmentWidget.swift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2p3gUZRNWW388rWFRjiU7
This commit is contained in:
alexandrev-tibco
2026-07-25 12:13:51 +02:00
parent 1a3fce0df4
commit 922710c53f
10 changed files with 524 additions and 74 deletions
@@ -62,6 +62,8 @@ struct DataGapsSheet: View {
@Environment(\.dismiss) private var dismiss
@State private var fillTarget: FillTarget?
@State private var filledGapIds: Set<String> = []
private let snapshotRepository = SnapshotRepository()
private struct FillTarget: Identifiable {
let source: InvestmentSource
@@ -69,15 +71,19 @@ struct DataGapsSheet: View {
var id: String { "\(source.id.uuidString)-\(date.timeIntervalSince1970)" }
}
private var visibleGaps: [SnapshotGapDetector.Gap] {
gaps.filter { !filledGapIds.contains($0.id) }
}
var body: some View {
NavigationStack {
List {
Section {
ForEach(gaps) { gap in
ForEach(visibleGaps) { gap in
gapRow(gap)
}
} footer: {
Text("The chart estimates these months. Add real snapshots to keep your history accurate.")
Text("Auto-fill estimates the missing months between two snapshots (marked as estimated). Or add the real values yourself.")
}
}
.navigationTitle("Missing Data")
@@ -116,14 +122,34 @@ struct DataGapsSheet: View {
if let source = sourceProvider(gap.sourceId),
let firstMissing = gap.missingMonthDates.first {
Button {
fillTarget = FillTarget(source: source, date: firstMissing)
} label: {
Label("Fill Gap", systemImage: "plus.circle.fill")
HStack(spacing: 16) {
Button {
let n = snapshotRepository.autoFillGap(
source: source,
from: gap.fromMonth,
to: gap.toMonth,
missingMonthDates: gap.missingMonthDates
)
if n > 0 { filledGapIds.insert(gap.id) }
} label: {
Label(
String(format: String(localized: "gaps_autofill_cta"), gap.missingMonths),
systemImage: "wand.and.stars"
)
.font(.subheadline.weight(.semibold))
}
.buttonStyle(.borderless)
.tint(.appPrimary)
Button {
fillTarget = FillTarget(source: source, date: firstMissing)
} label: {
Label("Add manually", systemImage: "plus.circle")
.font(.subheadline)
}
.buttonStyle(.borderless)
.tint(.secondary)
}
.buttonStyle(.borderless)
.tint(.appPrimary)
}
}
.padding(.vertical, 4)