From 4dde907f52b0c57bd1dc6703868d8e0880936700 Mon Sep 17 00:00:00 2001 From: davmar Date: Thu, 10 Sep 2026 18:56:50 +0200 Subject: [PATCH] widgets looking very ok --- lib/screens/overview_screen.dart | 398 ++++++++++++++++++++----------- 1 file changed, 259 insertions(+), 139 deletions(-) diff --git a/lib/screens/overview_screen.dart b/lib/screens/overview_screen.dart index f6f4d35..f1e6bd2 100644 --- a/lib/screens/overview_screen.dart +++ b/lib/screens/overview_screen.dart @@ -332,7 +332,7 @@ class _OverviewScreenState extends State { return ListView( physics: const AlwaysScrollableScrollPhysics(), - padding: const EdgeInsets.fromLTRB(0, 8, 0, 32), + padding: const EdgeInsets.fromLTRB(0, 4, 0, 24), children: [ if (bank.error != null) ...[ Text( @@ -341,7 +341,7 @@ class _OverviewScreenState extends State { color: theme.colorScheme.error, ), ), - const SizedBox(height: 12), + const SizedBox(height: 8), ], Row( children: [ @@ -355,23 +355,33 @@ class _OverviewScreenState extends State { ), TextButton( onPressed: _openFavouritesManager, + style: TextButton.styleFrom( + visualDensity: VisualDensity.compact, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: const EdgeInsets.symmetric(horizontal: 8), + ), child: const Text('Manage'), ), TextButton.icon( onPressed: () => setState(() => _showFavouritesView = false), - icon: const Icon(Icons.insights_outlined, size: 18), + style: TextButton.styleFrom( + visualDensity: VisualDensity.compact, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: const EdgeInsets.symmetric(horizontal: 8), + ), + icon: const Icon(Icons.open_in_new, size: 16), label: const Text('Full overview'), ), ], ), - const SizedBox(height: 4), - Text( - 'All favourites with Display checked appear here. ' - 'Use Full overview to set a filter, then ★ → Save current filter.', - style: theme.textTheme.labelMedium, - ), if (displayed.isEmpty) ...[ - const SizedBox(height: 24), + const SizedBox(height: 8), + Text( + 'All favourites with Display checked appear here. ' + 'Use Full overview to set a filter, then ★ → Save current filter.', + style: theme.textTheme.labelMedium, + ), + const SizedBox(height: 16), Text( 'No displayed favourites yet. Tap Manage to enable Display, ' 'or save a new filter from Full overview.', @@ -380,9 +390,9 @@ class _OverviewScreenState extends State { ), ), ] else ...[ - const SizedBox(height: 12), + const SizedBox(height: 8), for (var i = 0; i < displayed.length; i++) ...[ - if (i > 0) const SizedBox(height: 14), + if (i > 0) const SizedBox(height: 10), _favouriteChartCard( context: context, favourite: displayed[i], @@ -1176,10 +1186,13 @@ class _ChartCard extends StatelessWidget { final VoidCallback? onEdit; static const _compactSegmentStyle = ButtonStyle( - visualDensity: VisualDensity(horizontal: -4, vertical: -3), + visualDensity: VisualDensity(horizontal: -4, vertical: -4), tapTargetSize: MaterialTapTargetSize.shrinkWrap, - padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 8)), - minimumSize: WidgetStatePropertyAll(Size(0, 32)), + padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 6)), + minimumSize: WidgetStatePropertyAll(Size(0, 28)), + textStyle: WidgetStatePropertyAll( + TextStyle(fontSize: 12, fontWeight: FontWeight.w600), + ), ); Widget _filterButton(ColorScheme colors) { @@ -1216,44 +1229,152 @@ class _ChartCard extends StatelessWidget { ); } - Widget _editButton(ColorScheme colors) { + Widget _editButton(ColorScheme colors, {required bool compact}) { return IconButton( tooltip: 'Edit favourite', onPressed: onEdit, visualDensity: const VisualDensity(horizontal: -4, vertical: -4), - constraints: const BoxConstraints(minWidth: 36, minHeight: 36), + constraints: BoxConstraints( + minWidth: compact ? 28 : 36, + minHeight: compact ? 28 : 36, + ), padding: EdgeInsets.zero, style: IconButton.styleFrom( - backgroundColor: colors.surfaceContainerHigh, + backgroundColor: + compact ? null : colors.surfaceContainerHigh, foregroundColor: colors.onSurfaceVariant, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), - icon: const Icon(Icons.edit_outlined, size: 18), + icon: Icon(Icons.edit_outlined, size: compact ? 16 : 18), ); } + Widget _legendRow(ColorScheme colors, {required bool compact}) { + if (metric == OverviewMetricMode.flow) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + _LegendDot( + color: _kIncomeColor, + label: 'Income', + compact: compact, + ), + SizedBox(width: compact ? 10 : 14), + _LegendDot( + color: _kExpenseColor, + label: 'Expense', + compact: compact, + ), + ], + ); + } + if (labelSpend.isEmpty) return const SizedBox.shrink(); + return Wrap( + spacing: compact ? 8 : 12, + runSpacing: 2, + alignment: WrapAlignment.end, + children: [ + for (final row in labelSpend.take(compact ? 3 : 5)) + GestureDetector( + onTap: () => onLabelTap(row.label), + child: _LegendDot( + color: row.label.color, + label: row.label.name, + compact: compact, + ), + ), + if (labelSpend.length > (compact ? 3 : 5)) + _LegendDot( + color: colors.outline, + label: 'Other', + compact: compact, + ), + ], + ); + } + + Widget _periodControls(ThemeData theme, {required bool compact}) { + final titleStyle = (compact + ? theme.textTheme.labelMedium + : theme.textTheme.titleMedium) + ?.copyWith( + fontWeight: FontWeight.w600, + color: compact ? theme.colorScheme.onSurfaceVariant : null, + ); + + if (period == OverviewPeriodMode.monthly) { + return Row( + children: [ + IconButton( + tooltip: 'Previous year', + onPressed: canGoBack ? onBack : null, + visualDensity: const VisualDensity(horizontal: -4, vertical: -4), + constraints: BoxConstraints( + minWidth: compact ? 28 : 32, + minHeight: compact ? 28 : 32, + ), + padding: EdgeInsets.zero, + icon: Icon(Icons.chevron_left, size: compact ? 20 : 22), + ), + Flexible( + child: Text( + periodTitle, + textAlign: TextAlign.left, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: titleStyle, + ), + ), + IconButton( + tooltip: 'Next year', + onPressed: canGoForward ? onForward : null, + visualDensity: const VisualDensity(horizontal: -4, vertical: -4), + constraints: BoxConstraints( + minWidth: compact ? 28 : 32, + minHeight: compact ? 28 : 32, + ), + padding: EdgeInsets.zero, + icon: Icon(Icons.chevron_right, size: compact ? 20 : 22), + ), + ], + ); + } + + return Text(periodTitle, style: titleStyle); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); final colors = theme.colorScheme; final showFilter = metric == OverviewMetricMode.labels && onPickLabels != null; - final showFavouriteChrome = - onPeriodModeChanged != null || onEdit != null; + final compact = onPeriodModeChanged != null || onEdit != null; + final chartHeight = compact ? 176.0 : 220.0; + + final legend = _legendRow(colors, compact: compact); + final hasLegend = metric == OverviewMetricMode.flow || + labelSpend.isNotEmpty; return Card( color: colors.surfaceContainer, elevation: 0, + margin: EdgeInsets.zero, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(compact ? 16 : 20), ), child: Padding( - padding: const EdgeInsets.fromLTRB(14, 12, 14, 16), + padding: EdgeInsets.fromLTRB( + compact ? 12 : 14, + compact ? 10 : 12, + compact ? 10 : 14, + compact ? 12 : 16, + ), child: Column( children: [ - if (showFavouriteChrome) ...[ + if (compact) ...[ Row( children: [ if (onPeriodModeChanged != null) @@ -1274,145 +1395,133 @@ class _ChartCard extends StatelessWidget { onSelectionChanged: (selection) { onPeriodModeChanged!(selection.first); }, - ) - else - const SizedBox.shrink(), + ), if (heading != null) ...[ - const SizedBox(width: 10), + const SizedBox(width: 8), Expanded( child: Text( heading!, maxLines: 1, overflow: TextOverflow.ellipsis, - style: theme.textTheme.titleMedium?.copyWith( + style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w700, ), ), ), ] else const Spacer(), - if (onEdit != null) ...[ - const SizedBox(width: 6), - _editButton(colors), - ], - ], - ), - const SizedBox(height: 8), - ] else if (heading != null) ...[ - Align( - alignment: Alignment.centerLeft, - child: Text( - heading!, - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w700, - ), - ), - ), - const SizedBox(height: 8), - ], - if (period == OverviewPeriodMode.monthly) - Row( - children: [ - IconButton( - tooltip: 'Previous year', - onPressed: canGoBack ? onBack : null, - visualDensity: const VisualDensity( - horizontal: -4, - vertical: -4, - ), - constraints: const BoxConstraints( - minWidth: 32, - minHeight: 32, - ), - padding: EdgeInsets.zero, - icon: const Icon(Icons.chevron_left, size: 22), - ), - Expanded( - child: Text( - periodTitle, - textAlign: TextAlign.center, - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w700, - ), - ), - ), - IconButton( - tooltip: 'Next year', - onPressed: canGoForward ? onForward : null, - visualDensity: const VisualDensity( - horizontal: -4, - vertical: -4, - ), - constraints: const BoxConstraints( - minWidth: 32, - minHeight: 32, - ), - padding: EdgeInsets.zero, - icon: const Icon(Icons.chevron_right, size: 22), - ), if (showFilter) ...[ - const SizedBox(width: 2), + const SizedBox(width: 4), _filterButton(colors), ], + if (onEdit != null) ...[ + const SizedBox(width: 2), + _editButton(colors, compact: true), + ], ], - ) - else + ), + const SizedBox(height: 4), Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox(width: showFilter ? 36 : 0), Expanded( - child: Text( - periodTitle, - textAlign: TextAlign.center, - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w700, - ), + child: _periodControls(theme, compact: true), + ), + if (hasLegend) ...[ + const SizedBox(width: 8), + Flexible(child: legend), + ], + ], + ), + const SizedBox(height: 6), + ] else ...[ + if (heading != null) ...[ + Align( + alignment: Alignment.centerLeft, + child: Text( + heading!, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, ), ), - if (showFilter) - _filterButton(colors) - else - const SizedBox(width: 0), - ], - ), - if (metric == OverviewMetricMode.flow) ...[ - const Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _LegendDot(color: _kIncomeColor, label: 'Income'), - SizedBox(width: 14), - _LegendDot(color: _kExpenseColor, label: 'Expense'), - ], - ), - const SizedBox(height: 8), - ] else if (labelSpend.isNotEmpty) ...[ - Align( - alignment: Alignment.centerRight, - child: Wrap( - spacing: 12, - runSpacing: 4, - alignment: WrapAlignment.end, + ), + const SizedBox(height: 8), + ], + if (period == OverviewPeriodMode.monthly) + Row( children: [ - for (final row in labelSpend.take(5)) - GestureDetector( - onTap: () => onLabelTap(row.label), - child: _LegendDot( - color: row.label.color, - label: row.label.name, + IconButton( + tooltip: 'Previous year', + onPressed: canGoBack ? onBack : null, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -4, + ), + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + padding: EdgeInsets.zero, + icon: const Icon(Icons.chevron_left, size: 22), + ), + Expanded( + child: Text( + periodTitle, + textAlign: TextAlign.center, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, ), ), - if (labelSpend.length > 5) - _LegendDot( - color: colors.outline, - label: 'Other', + ), + IconButton( + tooltip: 'Next year', + onPressed: canGoForward ? onForward : null, + visualDensity: const VisualDensity( + horizontal: -4, + vertical: -4, ), + constraints: const BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + padding: EdgeInsets.zero, + icon: const Icon(Icons.chevron_right, size: 22), + ), + if (showFilter) ...[ + const SizedBox(width: 2), + _filterButton(colors), + ], + ], + ) + else + Row( + children: [ + SizedBox(width: showFilter ? 36 : 0), + Expanded( + child: Text( + periodTitle, + textAlign: TextAlign.center, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + ), + if (showFilter) + _filterButton(colors) + else + const SizedBox(width: 0), ], ), - ), - const SizedBox(height: 8), + if (hasLegend) ...[ + Align( + alignment: Alignment.centerRight, + child: legend, + ), + const SizedBox(height: 8), + ], ], SizedBox( - height: 220, + height: chartHeight, child: metric == OverviewMetricMode.flow ? _FlowBars( months: months, @@ -1908,23 +2017,34 @@ class _LabelMonthBars extends StatelessWidget { } class _LegendDot extends StatelessWidget { - const _LegendDot({required this.color, required this.label}); + const _LegendDot({ + required this.color, + required this.label, + this.compact = false, + }); final Color color; final String label; + final bool compact; @override Widget build(BuildContext context) { + final theme = Theme.of(context); return Row( mainAxisSize: MainAxisSize.min, children: [ Container( - width: 8, - height: 8, + width: compact ? 6 : 8, + height: compact ? 6 : 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), - const SizedBox(width: 6), - Text(label, style: Theme.of(context).textTheme.labelMedium), + SizedBox(width: compact ? 4 : 6), + Text( + label, + style: compact + ? theme.textTheme.labelSmall + : theme.textTheme.labelMedium, + ), ], ); }