better UI

This commit is contained in:
davmar
2026-09-09 14:38:17 +02:00
parent 903a2980de
commit ae464ff304
9 changed files with 771 additions and 471 deletions
+88 -87
View File
@@ -9,6 +9,7 @@ import '../models/bank.dart';
import '../models/transaction_meta.dart';
import '../providers/bank_provider.dart';
import '../providers/tx_meta_provider.dart';
import '../utils/app_theme.dart';
import '../utils/helpers.dart';
import '../utils/navigation.dart';
import '../utils/tx_calendar.dart';
@@ -24,9 +25,8 @@ enum _PeriodMode { monthly, yearly }
enum _MetricMode { flow, labels }
/// Distinct, intuitive colors (avoid two greens).
const _kIncomeColor = Color(0xFF2563EB); // blue
const _kExpenseColor = Color(0xFFDC2626); // red
const _kIncomeColor = AppTheme.accent;
const _kExpenseColor = AppTheme.expense;
class OverviewScreen extends StatefulWidget {
const OverviewScreen({super.key});
@@ -282,7 +282,7 @@ class _OverviewScreenState extends State<OverviewScreen> {
Expanded(
child: _SummaryCard(
icon: Icons.south_west,
iconBackground: _kIncomeColor.withValues(alpha: 0.12),
iconBackground: _kIncomeColor.withValues(alpha: 0.18),
iconColor: _kIncomeColor,
amount: formatMoney(summaryFlow.income, currency),
label: 'Income',
@@ -292,7 +292,7 @@ class _OverviewScreenState extends State<OverviewScreen> {
Expanded(
child: _SummaryCard(
icon: Icons.north_east,
iconBackground: _kExpenseColor.withValues(alpha: 0.12),
iconBackground: _kExpenseColor.withValues(alpha: 0.18),
iconColor: _kExpenseColor,
amount: formatMoney(summaryFlow.expense, currency),
label: 'Expenses',
@@ -300,19 +300,12 @@ class _OverviewScreenState extends State<OverviewScreen> {
),
],
),
const SizedBox(height: 8),
Text(
_period == _PeriodMode.monthly
? 'Totals for ${_cursor.year}'
: 'Totals across all years',
style: theme.textTheme.labelMedium,
),
] else ...[
_SummaryCard(
icon: Icons.label_outline,
iconBackground:
theme.colorScheme.secondaryContainer.withValues(alpha: 0.6),
iconColor: theme.colorScheme.onSecondaryContainer,
theme.colorScheme.primary.withValues(alpha: 0.16),
iconColor: theme.colorScheme.primary,
amount: formatMoney(
labelSpend.fold<double>(0, (s, e) => s + e.amount),
currency,
@@ -324,12 +317,14 @@ class _OverviewScreenState extends State<OverviewScreen> {
},
),
],
const SizedBox(height: 20),
const SizedBox(height: 22),
Text(
_metric == _MetricMode.flow
? 'History · Income & expenses'
: 'History · Labels',
style: theme.textTheme.titleLarge,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
@@ -672,35 +667,29 @@ class _ChartCard extends StatelessWidget {
final colors = theme.colorScheme;
return Card(
color: colors.surface,
color: colors.surfaceContainer,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: BorderSide(color: colors.outlineVariant.withValues(alpha: 0.5)),
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 16),
padding: const EdgeInsets.fromLTRB(14, 14, 14, 16),
child: Column(
children: [
Row(
children: [
SegmentedButton<_PeriodMode>(
showSelectedIcon: false,
style: ButtonStyle(
visualDensity: const VisualDensity(
style: const ButtonStyle(
visualDensity: VisualDensity(
horizontal: -4,
vertical: -3,
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 8),
),
minimumSize: const WidgetStatePropertyAll(Size(0, 28)),
textStyle: WidgetStatePropertyAll(
theme.textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.w600,
),
padding: WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 10),
),
minimumSize: WidgetStatePropertyAll(Size(0, 32)),
),
segments: const [
ButtonSegment(
@@ -718,7 +707,7 @@ class _ChartCard extends StatelessWidget {
},
),
if (metric == _MetricMode.labels) ...[
const SizedBox(width: 2),
const SizedBox(width: 4),
IconButton(
tooltip: includedLabelCount == null
? 'Filter labels ($availableLabelCount)'
@@ -729,46 +718,41 @@ class _ChartCard extends StatelessWidget {
vertical: -4,
),
constraints: const BoxConstraints(
minWidth: 28,
minHeight: 28,
minWidth: 32,
minHeight: 32,
),
padding: EdgeInsets.zero,
style: IconButton.styleFrom(
backgroundColor: includedLabelCount == null
? null
: colors.secondaryContainer,
? colors.surfaceContainerHigh
: colors.primary.withValues(alpha: 0.22),
foregroundColor: includedLabelCount == null
? colors.onSurfaceVariant
: colors.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
icon: Badge(
isLabelVisible: includedLabelCount != null,
smallSize: 6,
child: Icon(
Icons.filter_list,
size: 18,
color: includedLabelCount == null
? colors.onSurfaceVariant
: colors.onSecondaryContainer,
),
child: const Icon(Icons.filter_list, size: 18),
),
),
],
const Spacer(),
SegmentedButton<_MetricMode>(
showSelectedIcon: false,
style: ButtonStyle(
visualDensity: const VisualDensity(
style: const ButtonStyle(
visualDensity: VisualDensity(
horizontal: -4,
vertical: -3,
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 8),
),
minimumSize: const WidgetStatePropertyAll(Size(0, 28)),
textStyle: WidgetStatePropertyAll(
theme.textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.w600,
),
padding: WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 10),
),
minimumSize: WidgetStatePropertyAll(Size(0, 32)),
),
segments: const [
ButtonSegment(
@@ -787,7 +771,7 @@ class _ChartCard extends StatelessWidget {
),
],
),
const SizedBox(height: 6),
const SizedBox(height: 8),
if (period == _PeriodMode.monthly)
Row(
children: [
@@ -809,8 +793,8 @@ class _ChartCard extends StatelessWidget {
child: Text(
periodTitle,
textAlign: TextAlign.center,
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
),
@@ -836,8 +820,8 @@ class _ChartCard extends StatelessWidget {
child: Text(
periodTitle,
textAlign: TextAlign.center,
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
),
@@ -1016,8 +1000,9 @@ class _FlowBars extends StatelessWidget {
drawVerticalLine: false,
horizontalInterval: safeMax / 3,
getDrawingHorizontalLine: (value) => FlLine(
color: colors.outlineVariant.withValues(alpha: 0.45),
color: colors.outlineVariant.withValues(alpha: 0.35),
strokeWidth: 1,
dashArray: const [4, 4],
),
),
borderData: FlBorderData(show: false),
@@ -1209,8 +1194,9 @@ class _LabelMonthBars extends StatelessWidget {
drawVerticalLine: false,
horizontalInterval: safeMax / 3,
getDrawingHorizontalLine: (value) => FlLine(
color: colors.outlineVariant.withValues(alpha: 0.45),
color: colors.outlineVariant.withValues(alpha: 0.35),
strokeWidth: 1,
dashArray: const [4, 4],
),
),
borderData: FlBorderData(show: false),
@@ -1291,26 +1277,25 @@ class _SummaryCard extends StatelessWidget {
final colors = theme.colorScheme;
return Card(
color: colors.surface,
color: colors.surfaceContainer,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: colors.outlineVariant.withValues(alpha: 0.5)),
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 14, 12, 14),
padding: const EdgeInsets.fromLTRB(14, 14, 14, 14),
child: Row(
children: [
Container(
width: 40,
height: 40,
width: 42,
height: 42,
decoration: BoxDecoration(
color: iconBackground,
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: iconColor, size: 22),
),
const SizedBox(width: 10),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -1321,9 +1306,18 @@ class _SummaryCard extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: colors.onSurface,
),
),
const SizedBox(height: 2),
Text(
label.toUpperCase(),
style: theme.textTheme.labelSmall?.copyWith(
letterSpacing: 0.6,
fontWeight: FontWeight.w600,
color: colors.onSurfaceVariant,
),
),
Text(label, style: theme.textTheme.labelMedium),
],
),
),
@@ -1355,16 +1349,16 @@ class _HistoryCard extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colors = theme.colorScheme;
final isYear = title.length == 4 && int.tryParse(title) != null;
return Card(
color: colors.surface,
color: colors.surfaceContainer,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
side: BorderSide(color: colors.outlineVariant.withValues(alpha: 0.5)),
borderRadius: BorderRadius.circular(16),
),
child: InkWell(
borderRadius: BorderRadius.circular(18),
borderRadius: BorderRadius.circular(16),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
@@ -1375,16 +1369,19 @@ class _HistoryCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
month.month == 1 && title.length == 4 ? 'Year' : 'Month',
style: theme.textTheme.labelMedium,
isYear ? 'YEAR' : 'MONTH',
style: theme.textTheme.labelSmall?.copyWith(
letterSpacing: 0.8,
fontWeight: FontWeight.w600,
color: colors.onSurfaceVariant,
),
),
const SizedBox(height: 2),
const SizedBox(height: 4),
Text(
title.length == 4 && int.tryParse(title) != null
? title
: formatMonthYear(month),
isYear ? title : formatMonthYear(month),
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: colors.onSurface,
),
),
],
@@ -1397,15 +1394,15 @@ class _HistoryCard extends StatelessWidget {
formatMoney(income, currency),
style: theme.textTheme.titleSmall?.copyWith(
color: _kIncomeColor,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 2),
const SizedBox(height: 4),
Text(
formatMoney(-expense, currency),
style: theme.textTheme.titleSmall?.copyWith(
color: _kExpenseColor,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
),
),
],
@@ -1444,11 +1441,10 @@ class _LabelHistoryCard extends StatelessWidget {
final total = rows.fold<double>(0, (sum, row) => sum + row.amount);
return Card(
color: colors.surface,
color: colors.surfaceContainer,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
side: BorderSide(color: colors.outlineVariant.withValues(alpha: 0.5)),
borderRadius: BorderRadius.circular(16),
),
clipBehavior: Clip.antiAlias,
child: Column(
@@ -1465,14 +1461,19 @@ class _LabelHistoryCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isYear ? 'Year' : 'Month',
style: theme.textTheme.labelMedium,
isYear ? 'YEAR' : 'MONTH',
style: theme.textTheme.labelSmall?.copyWith(
letterSpacing: 0.8,
fontWeight: FontWeight.w600,
color: colors.onSurfaceVariant,
),
),
const SizedBox(height: 2),
const SizedBox(height: 4),
Text(
periodLabel,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: colors.onSurface,
),
),
],
@@ -1482,7 +1483,7 @@ class _LabelHistoryCard extends StatelessWidget {
formatMoney(-total, currency),
style: theme.textTheme.titleSmall?.copyWith(
color: _kExpenseColor,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
),
),
],