#3 Quick Update paste/scan por campo: - Barra de teclado con 'Pegar €X' (si hay importe en portapapeles) y 'Escanear' (PhotosPicker → OCR on-device). Actúan sobre el campo enfocado. - ImageAmountScanner nuevo (app target, Vision) espejo del scanner del Share Extension: candidatos rankeados por prominencia, filtra % y años. - 1 candidato → rellena directo; varios → confirmationDialog para elegir. - Strings ×7. #4 Swipe entre charts (iPhone): - Gesto horizontal en el área del chart avanza al chart anterior/siguiente en el orden del menú de título (respeta showForecast). Indicador de página con puntos. Transición direccional. selectChart ya no bloquea premium (muestra teaser), así que deslizar a uno premium funciona. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
This commit is contained in:
@@ -604,6 +604,14 @@ struct ChartsContainerView: View {
|
||||
}
|
||||
iPhonePeriodControl
|
||||
chartContent
|
||||
// Swipe left/right to move to the adjacent chart type.
|
||||
.gesture(chartSwipeGesture)
|
||||
.id(viewModel.selectedChartType)
|
||||
.transition(.asymmetric(
|
||||
insertion: .move(edge: swipeInsertionEdge).combined(with: .opacity),
|
||||
removal: .opacity
|
||||
))
|
||||
chartPageIndicator
|
||||
if viewModel.hiddenHistoryMonths > 0 {
|
||||
lockedHistoryTeaser
|
||||
}
|
||||
@@ -614,6 +622,45 @@ struct ChartsContainerView: View {
|
||||
.sheet(isPresented: $viewModel.showingPaywall) { PaywallView() }
|
||||
}
|
||||
|
||||
// MARK: - iPhone swipe between charts
|
||||
|
||||
/// Chart types in the same grouped order as the title switcher menu.
|
||||
private var orderedChartTypes: [ChartsViewModel.ChartType] {
|
||||
Self.chartGroups.flatMap { $0.types }.filter { showForecast || $0 != .prediction }
|
||||
}
|
||||
|
||||
@State private var swipeInsertionEdge: Edge = .trailing
|
||||
|
||||
private var chartSwipeGesture: some Gesture {
|
||||
DragGesture(minimumDistance: 30)
|
||||
.onEnded { value in
|
||||
// Horizontal-dominant swipes only, so vertical scroll still works.
|
||||
guard abs(value.translation.width) > abs(value.translation.height) * 1.5 else { return }
|
||||
let ordered = orderedChartTypes
|
||||
guard let idx = ordered.firstIndex(of: viewModel.selectedChartType) else { return }
|
||||
if value.translation.width < 0, idx < ordered.count - 1 {
|
||||
swipeInsertionEdge = .trailing
|
||||
withAnimation(.snappy) { viewModel.selectChart(ordered[idx + 1]) }
|
||||
} else if value.translation.width > 0, idx > 0 {
|
||||
swipeInsertionEdge = .leading
|
||||
withAnimation(.snappy) { viewModel.selectChart(ordered[idx - 1]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var chartPageIndicator: some View {
|
||||
let ordered = orderedChartTypes
|
||||
let current = ordered.firstIndex(of: viewModel.selectedChartType) ?? 0
|
||||
return HStack(spacing: 5) {
|
||||
ForEach(ordered.indices, id: \.self) { i in
|
||||
Circle()
|
||||
.fill(i == current ? Color.appPrimary : Color.secondary.opacity(0.25))
|
||||
.frame(width: i == current ? 7 : 5, height: i == current ? 7 : 5)
|
||||
}
|
||||
}
|
||||
.animation(.snappy, value: current)
|
||||
}
|
||||
|
||||
/// Stocks-style segmented period control above the chart (the chart switcher
|
||||
/// lives in the title menu, filters in the toolbar).
|
||||
@ViewBuilder
|
||||
|
||||
Reference in New Issue
Block a user