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> getAccountBalance() async { final coreCubit = GetIt.I.get(); 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 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(); balances[0] = totalBalance; for (final idx in List.generate(29, (i) => i + 1)) { balances[idx] = balances[idx - 1]! + differences[idx]!; } List 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!, ), ], ), ); }, ), ), ], ), ), ); } }