okane/lib/ui/pages/account/balance_graph_card.dart

151 lines
5.1 KiB
Dart

import 'dart:collection';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
double getBalanceGraphScaling(double maxBalance) {
if (maxBalance < 100) {
return 10;
} else if (maxBalance < 1000) {
return 200;
} else if (maxBalance < 10000) {
return 1000;
}
return 10000;
}
class AccountBalanceGraphCard extends StatelessWidget {
const AccountBalanceGraphCard({super.key});
Future<List<FlSpot>> getAccountBalance() async {
final coreCubit = GetIt.I.get<CoreCubit>();
final today = toMidnight(DateTime.now());
final transactions = await getLastTransactions(
coreCubit.activeAccount!,
today,
30,
);
final totalBalance = await getTotalBalance(coreCubit.activeAccount!);
// Compute the differences per day
Map<int, double> differences = Map.fromEntries(
List.generate(30, (i) => i).map((i) => MapEntry(i, 0)),
);
for (final item in transactions) {
final diff = today.difference(toMidnight(item.date)).inDays;
final balance = differences[diff]!;
differences[diff] = balance + item.amount;
}
// Compute the balance
final balances = HashMap<int, double>();
balances[0] = totalBalance;
for (final idx in List.generate(29, (i) => i + 1)) {
balances[idx] = balances[idx - 1]! + differences[idx]!;
}
List<FlSpot> result = List.empty(growable: true);
result.add(FlSpot(0, totalBalance));
result.addAll(
List.generate(
28,
(i) => i + 1,
).map((i) => FlSpot(i.toDouble(), balances[i]!)),
);
return result;
}
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text("Account balance"),
SizedBox(
height: 150,
child: FutureBuilder(
future: getAccountBalance(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final today = DateTime.now();
final maxBalance = snapshot.data!
.map((spot) => spot.y)
.reduce((acc, y) => y > acc ? y : acc);
return LineChart(
LineChartData(
minY:
snapshot.data!
.map((spot) => spot.y)
.reduce((acc, y) => y < acc ? y : acc) -
20,
maxY: maxBalance + 20,
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
interval: 4,
showTitles: true,
reservedSize: 40,
getTitlesWidget: (val, meta) {
final day = today.subtract(
Duration(days: val.toInt()),
);
return SideTitleWidget(
meta: meta,
child: Text(
formatDateTime(day, formatYear: false),
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 50,
interval: getBalanceGraphScaling(maxBalance),
getTitlesWidget:
(value, meta) => SideTitleWidget(
meta: meta,
child: Text(
formatCurrency(value, precise: false),
),
),
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
gridData: const FlGridData(show: false),
lineBarsData: [
LineChartBarData(
isCurved: false,
barWidth: 3,
dotData: const FlDotData(show: false),
spots: snapshot.data!,
),
],
),
);
},
),
),
],
),
),
);
}
}