ynab_lite/lib/screens/dashboard_screen.dart

153 lines
4.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../providers/db_provider.dart';
import '../theme/app_theme.dart';
class DashboardScreen extends ConsumerWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final totalBalance = ref.watch(totalBalanceProvider);
final accounts = ref.watch(openAccountsProvider);
final recentTxs = ref.watch(recentTransactionsProvider);
return ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
children: [
// Total Balance
_BalanceCard(balance: totalBalance),
const SizedBox(height: 16),
// By accounts
Text('Comptes', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
...accounts.map((a) => _AccountMiniCard(account: a)),
const SizedBox(height: 24),
// Recent transactions
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Transactions récentes', style: Theme.of(context).textTheme.titleMedium),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.arrow_forward_ios, size: 14),
label: const Text('Voir tout'),
),
],
),
const SizedBox(height: 8),
if (recentTxs.isEmpty)
Card(
child: Padding(
padding: const EdgeInsets.all(32),
child: Center(child: Text('Aucune transaction', style: Theme.of(context).textTheme.bodyMedium)),
),
)
else
...recentTxs.map((t) => _TxItem(transaction: t)),
],
);
}
}
class _BalanceCard extends StatelessWidget {
final double balance;
const _BalanceCard({required this.balance});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Solde total', style: Theme.of(context).textTheme.bodyMedium),
const SizedBox(height: 8),
Text(
formatCurrency(balance),
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontSize: 36,
fontWeight: FontWeight.w700,
color: balance >= 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error,
),
),
],
),
),
);
}
}
class _AccountMiniCard extends StatelessWidget {
final Account account;
const _AccountMiniCard({required this.account});
IconData _icon() {
switch (account.type) {
case 'savings': return Icons.savings_outlined;
case 'credit': return Icons.credit_card_outlined;
case 'cash': return Icons.money_outlined;
default: return Icons.account_balance_outlined;
}
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 6),
child: ListTile(
dense: true,
leading: Icon(_icon(), color: Theme.of(context).colorScheme.onSurfaceVariant),
title: Text(account.name, style: Theme.of(context).textTheme.bodyMedium),
trailing: Text(
formatCurrency(account.balance),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: account.balance >= 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error,
),
),
),
);
}
}
class _TxItem extends StatelessWidget {
final Transaction transaction;
const _TxItem({required this.transaction});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 4),
child: ListTile(
dense: true,
leading: CircleAvatar(
radius: 18,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: Icon(
transaction.amount >= 0 ? Icons.arrow_downward : Icons.arrow_upward,
size: 16,
color: transaction.amount >= 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error,
),
),
title: Text(transaction.payee, style: Theme.of(context).textTheme.bodyMedium),
subtitle: Text(
DateFormat('dd/MM/yy').format(transaction.date),
style: Theme.of(context).textTheme.bodySmall,
),
trailing: Text(
formatCurrency(transaction.amount),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: transaction.amount >= 0 ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.error,
),
),
),
);
}
}