look aheead limit defined by user

This commit is contained in:
davmar
2026-09-10 09:31:11 +02:00
parent 200ff72e2e
commit 7437cc90ce
5 changed files with 92 additions and 24 deletions
+47 -24
View File
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import '../models/bank.dart';
import '../models/transaction_meta.dart';
import '../providers/bank_provider.dart';
import '../providers/preferences_provider.dart';
import '../providers/tx_meta_provider.dart';
import '../screens/transaction_detail_screen.dart';
import '../utils/app_theme.dart';
@@ -37,8 +38,6 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
List<RecurringItem>? _cachedItems;
int? _itemsCacheKey;
static const _maxYearsAhead = 5;
@override
void initState() {
super.initState();
@@ -51,11 +50,6 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
DateTime get _minMonth => monthStart(_today);
DateTime get _maxMonth {
final t = _today;
return DateTime(t.year + _maxYearsAhead, t.month);
}
/// Inclusive end date for the selected period.
DateTime get _through {
if (_period == _PeriodMode.yearly) {
@@ -69,50 +63,67 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
return formatMonthYear(_cursor);
}
bool get _canGoBack {
DateTime _maxMonthFor(int maxYears) {
final t = _today;
return DateTime(t.year + maxYears, t.month);
}
bool _canGoBack() {
if (_period == _PeriodMode.yearly) {
return _cursor.year > _today.year;
}
return monthStart(_cursor).isAfter(_minMonth);
}
bool get _canGoForward {
bool _canGoForward(int maxYears) {
if (_period == _PeriodMode.yearly) {
return _cursor.year < _today.year + _maxYearsAhead;
return _cursor.year < _today.year + maxYears;
}
return monthStart(_cursor).isBefore(_maxMonth);
return monthStart(_cursor).isBefore(_maxMonthFor(maxYears));
}
void _setPeriod(_PeriodMode mode) {
void _clampCursor(int maxYears) {
if (_period == _PeriodMode.yearly) {
if (_cursor.year > _today.year + maxYears) {
_cursor = DateTime(_today.year + maxYears);
}
} else {
final maxMonth = _maxMonthFor(maxYears);
if (_cursor.isAfter(maxMonth)) _cursor = maxMonth;
}
}
void _setPeriod(_PeriodMode mode, int maxYears) {
if (mode == _period) return;
setState(() {
_period = mode;
if (mode == _PeriodMode.yearly) {
_cursor = DateTime(_cursor.year);
if (_cursor.year < _today.year) _cursor = DateTime(_today.year);
if (_cursor.year > _today.year + _maxYearsAhead) {
_cursor = DateTime(_today.year + _maxYearsAhead);
if (_cursor.year > _today.year + maxYears) {
_cursor = DateTime(_today.year + maxYears);
}
} else {
final month = DateTime(_cursor.year, _today.month);
_cursor = month.isBefore(_minMonth) ? _minMonth : monthStart(month);
if (_cursor.isAfter(_maxMonth)) _cursor = _maxMonth;
final maxMonth = _maxMonthFor(maxYears);
if (_cursor.isAfter(maxMonth)) _cursor = maxMonth;
}
});
}
void _go(int delta) {
void _go(int delta, int maxYears) {
setState(() {
if (_period == _PeriodMode.yearly) {
final next = DateTime(_cursor.year + delta);
if (next.year < _today.year ||
next.year > _today.year + _maxYearsAhead) {
if (next.year < _today.year || next.year > _today.year + maxYears) {
return;
}
_cursor = next;
} else {
final next = addMonths(monthStart(_cursor), delta);
if (next.isBefore(_minMonth) || next.isAfter(_maxMonth)) return;
final maxMonth = _maxMonthFor(maxYears);
if (next.isBefore(_minMonth) || next.isAfter(maxMonth)) return;
_cursor = next;
}
});
@@ -131,6 +142,18 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
Widget _buildContent(BuildContext context) {
final bank = context.watch<BankController>();
final meta = context.watch<TxMetaController>();
final maxYears =
context.watch<PreferencesController>().lookAheadMaxYears;
final maxMonth = _maxMonthFor(maxYears);
final needsClamp = _period == _PeriodMode.yearly
? _cursor.year > _today.year + maxYears
: _cursor.isAfter(maxMonth);
if (needsClamp) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
setState(() => _clampCursor(maxYears));
});
}
final theme = Theme.of(context);
final colors = theme.colorScheme;
@@ -202,15 +225,15 @@ class _LookAheadScreenState extends State<LookAheadScreen> {
children: [
_PeriodToolbar(
period: _period,
onPeriodChanged: _setPeriod,
onPeriodChanged: (mode) => _setPeriod(mode, maxYears),
),
const SizedBox(height: 10),
_PeriodNavigator(
title: _periodTitle,
canGoBack: _canGoBack,
canGoForward: _canGoForward,
onBack: () => _go(-1),
onForward: () => _go(1),
canGoBack: _canGoBack(),
canGoForward: _canGoForward(maxYears),
onBack: () => _go(-1, maxYears),
onForward: () => _go(1, maxYears),
backTooltip:
_period == _PeriodMode.yearly ? 'Previous year' : 'Previous month',
forwardTooltip:
+27
View File
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/bank_provider.dart';
import '../providers/preferences_provider.dart';
import '../providers/theme_provider.dart';
import '../utils/connect_bank.dart';
import '../utils/constants.dart';
@@ -110,6 +111,7 @@ class SettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeController = context.watch<ThemeController>();
final prefs = context.watch<PreferencesController>();
final bank = context.watch<BankController>();
final theme = Theme.of(context);
@@ -150,6 +152,31 @@ class SettingsScreen extends StatelessWidget {
},
),
const SizedBox(height: 28),
Text('Look ahead', style: theme.textTheme.titleMedium),
const SizedBox(height: 8),
Card(
child: ListTile(
leading: const Icon(Icons.event_repeat_outlined),
title: const Text('Projection horizon'),
subtitle: Text(
'Navigate up to ${prefs.lookAheadMaxYears} year'
'${prefs.lookAheadMaxYears == 1 ? '' : 's'} ahead',
),
trailing: DropdownButton<int>(
value: prefs.lookAheadMaxYears,
underline: const SizedBox.shrink(),
items: [
for (final y in const [1, 2, 3, 5, 7, 10, 15, 20, 30])
DropdownMenuItem(value: y, child: Text('$y')),
],
onChanged: (value) {
if (value == null) return;
prefs.setLookAheadMaxYears(value);
},
),
),
),
const SizedBox(height: 28),
Text('Backend', style: theme.textTheme.titleMedium),
const SizedBox(height: 8),
Card(