240 lines
7.8 KiB
Dart
240 lines
7.8 KiB
Dart
import 'package:fl_chart/fl_chart.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// Eine Seite, die das Dashboard der App darstellt.
|
|
///
|
|
/// Diese Seite zeigt eine Übersicht über den aktuellen Kontostand,
|
|
/// die Entwicklung des Kontostands der letzten Monate sowie
|
|
/// die letzten Transaktionen.
|
|
class Dashboard extends StatelessWidget {
|
|
/// Erstellt eine neue Instanz der Dashboard-Seite.
|
|
const Dashboard({super.key});
|
|
|
|
/// Baut das Dashboard-Widget auf.
|
|
/// [context] ist der Build-Kontext
|
|
@override
|
|
Widget build(final BuildContext context) {
|
|
const double currentBalance = 4820.75;
|
|
const double previousMonthBalance = 4300;
|
|
|
|
final Map<String, double> monthlyBalance = <String, double>{
|
|
'Jan': 1200.0,
|
|
'Feb': 900.0,
|
|
'Mär': 1100.0,
|
|
'Apr': 950.0,
|
|
'Mai': 1300.0,
|
|
'Jun': 1050.0,
|
|
};
|
|
|
|
final List<Map<String, Object>> recentTransactions = <Map<String, Object>>[
|
|
<String, Object>{'name': 'Supermarkt', 'amount': -45.50},
|
|
<String, Object>{'name': 'Gehalt', 'amount': 2500.00},
|
|
<String, Object>{'name': 'Miete', 'amount': -900.00},
|
|
<String, Object>{'name': 'Streaming', 'amount': -12.99},
|
|
<String, Object>{'name': 'Kaffee', 'amount': -4.50},
|
|
];
|
|
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
_currentBalance(currentBalance, previousMonthBalance, context),
|
|
const SizedBox(height: 32),
|
|
|
|
const Text(
|
|
'Kontostand pro Monat',
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 12),
|
|
_monthlyBalance(monthlyBalance, context),
|
|
|
|
const SizedBox(height: 32),
|
|
const Text(
|
|
'Letzte Transaktionen',
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 12),
|
|
..._recentTransactions(recentTransactions),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Baut das Widget für den aktuellen Kontostand
|
|
/// und die Differenz zum Vormonat auf.
|
|
///
|
|
/// [currentBalance] ist der aktuelle Kontostand
|
|
/// [previousBalance] ist der Kontostand des Vormonats
|
|
/// [context] ist der Build-Kontext
|
|
Widget _currentBalance(
|
|
final double currentBalance,
|
|
final double previousBalance,
|
|
final BuildContext context,
|
|
) {
|
|
final ThemeData theme = Theme.of(context);
|
|
|
|
final double diff = currentBalance - previousBalance;
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: theme.colorScheme.primaryContainer,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: <Widget>[
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
Text('Aktuell', style: theme.textTheme.bodyMedium),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'${currentBalance.toStringAsFixed(2)} €',
|
|
style: theme.textTheme.headlineSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: <Widget>[
|
|
Text(
|
|
'Differenz zum Vormonat',
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: <Widget>[
|
|
Icon(
|
|
diff >= 0 ? Icons.arrow_upward : Icons.arrow_downward,
|
|
color: diff >= 0 ? Colors.green : Colors.red,
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
'${diff.abs().toStringAsFixed(2)} €',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: diff >= 0 ? Colors.green : Colors.red,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Baut das Widget für die Entwicklung
|
|
/// des Kontostands der letzten Monate auf.
|
|
///
|
|
/// [monthlyBalance] ist ein Map mit den Monaten als Schlüssel und
|
|
/// den dazugehörigen Kontoständen als Werte.
|
|
/// [context] ist der Build-Kontext.
|
|
Widget _monthlyBalance(
|
|
final Map<String, double> monthlyBalance,
|
|
final BuildContext context,
|
|
) {
|
|
final ThemeData theme = Theme.of(context);
|
|
final double maxY =
|
|
monthlyBalance.values.reduce(
|
|
(final double a, final double b) => a > b ? a : b,
|
|
) +
|
|
200;
|
|
|
|
return SizedBox(
|
|
height: 180,
|
|
child: LineChart(
|
|
LineChartData(
|
|
minY: 0,
|
|
maxY: maxY,
|
|
titlesData: FlTitlesData(
|
|
topTitles: const AxisTitles(),
|
|
rightTitles: const AxisTitles(),
|
|
bottomTitles: AxisTitles(
|
|
sideTitles: SideTitles(
|
|
showTitles: true,
|
|
reservedSize: 28,
|
|
interval: 1,
|
|
getTitlesWidget: (final double value, final TitleMeta meta) {
|
|
final List<String> months = monthlyBalance.keys.toList();
|
|
if (value.toInt() >= 0 && value.toInt() < months.length) {
|
|
return Text(months[value.toInt()]);
|
|
}
|
|
return const Text('');
|
|
},
|
|
),
|
|
),
|
|
leftTitles: AxisTitles(
|
|
sideTitles: SideTitles(
|
|
showTitles: true,
|
|
reservedSize: 50,
|
|
getTitlesWidget: (final double value, final TitleMeta meta) =>
|
|
Text(
|
|
'${value.toInt()} €',
|
|
style: const TextStyle(fontSize: 12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
lineBarsData: <LineChartBarData>[
|
|
LineChartBarData(
|
|
spots: List<FlSpot>.generate(
|
|
monthlyBalance.length,
|
|
(final int index) => FlSpot(
|
|
index.toDouble(),
|
|
monthlyBalance.values.elementAt(index),
|
|
),
|
|
),
|
|
isCurved: true,
|
|
barWidth: 3,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Erstellt Widgets für die letzten Transaktionen.
|
|
///
|
|
/// [recentTransactions] ist eine Liste von Transaktionen,
|
|
/// wobei jede Transaktion ein Map mit den Keys 'name' (String)
|
|
/// und 'amount' (double) ist.
|
|
/// Die Funktion gibt eine Liste von Widgets zurück,
|
|
/// die die Transaktionen anzeigen.
|
|
List<Widget> _recentTransactions(
|
|
final List<Map<String, Object>> recentTransactions,
|
|
) => recentTransactions
|
|
.map(
|
|
(final Map<String, Object> tx) => Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
title: Text((tx['name'] ?? '') as String),
|
|
trailing: Text(
|
|
'${((tx['amount'] ?? 0) as double).abs().toStringAsFixed(2)} €',
|
|
style: TextStyle(
|
|
color: ((tx['amount'] ?? 0) as double) >= 0
|
|
? Colors.green
|
|
: Colors.red,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
.toList();
|
|
}
|