152 lines
5.2 KiB
Dart
152 lines
5.2 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) {
|
|
final absMaxBalance = maxBalance.abs();
|
|
if (absMaxBalance < 100) {
|
|
return 10;
|
|
} else if (absMaxBalance < 1000) {
|
|
return 200;
|
|
} else if (absMaxBalance < 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!,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|