Skip to content

Commit

Permalink
HomeMain: add bills due the next 7 days
Browse files Browse the repository at this point in the history
First draft, ref #133
  • Loading branch information
dreautall committed Oct 29, 2023
1 parent 196ae06 commit bf77d91
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 10 deletions.
16 changes: 16 additions & 0 deletions lib/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,19 @@ extension AccountTypeFilterIcon on AccountTypeFilter {
}
}
}

extension BillAmountAvg on Bill {
double avgAmount() {
final double amountMax = (double.tryParse(this.amountMax) ?? 0).abs();
final double amountMin = (double.tryParse(this.amountMin) ?? 0).abs();
if (amountMax == 0) {
return amountMin;
}
if (amountMin == 0) {
return amountMax;
}

// Same as Firefly Source Code
return (amountMin + amountMax) / 2;
}
}
210 changes: 200 additions & 10 deletions lib/pages/home/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ class _HomeMainState extends State<HomeMain>
}

Future<bool> _fetchLastDays() async {
/*if (lastDaysExpense.isNotEmpty) { // :DEBUG:
if (lastDaysExpense.isNotEmpty) {
// :DEBUG:
return true;
}*/
}

final FireflyIii api = context.read<FireflyService>().api;

Expand Down Expand Up @@ -182,9 +183,10 @@ class _HomeMainState extends State<HomeMain>
}

Future<bool> _fetchOverviewChart() async {
/*if (overviewChartData.isNotEmpty) { // :DEBUG:
if (overviewChartData.isNotEmpty) {
// :DEBUG:
return true;
}*/
}

final FireflyIii api = context.read<FireflyService>().api;

Expand Down Expand Up @@ -217,9 +219,10 @@ class _HomeMainState extends State<HomeMain>
}

Future<bool> _fetchLastMonths() async {
/*if (lastMonthsExpense.isNotEmpty) { // :DEBUG:
if (lastMonthsExpense.isNotEmpty) {
// :DEBUG:
return true;
}*/
}

final FireflyIii api = context.read<FireflyService>().api;

Expand Down Expand Up @@ -305,9 +308,10 @@ class _HomeMainState extends State<HomeMain>
}

Future<bool> _fetchCategories() async {
/*if (catChartData.isNotEmpty) { // :DEBUG:
if (catChartData.isNotEmpty) {
// :DEBUG:
return true;
}*/
}

final FireflyIii api = context.read<FireflyService>().api;

Expand Down Expand Up @@ -429,10 +433,44 @@ class _HomeMainState extends State<HomeMain>
return respBudgets.body!.data;
}

Future<List<BillRead>> _fetchBills() async {
final FireflyIii api = context.read<FireflyService>().api;

final DateTime now = DateTime.now().toLocal().clearTime();
final DateTime end = now.copyWith(day: now.day + 7);

final Response<BillArray> respBills = await api.v1BillsGet(
start: DateFormat('yyyy-MM-dd', 'en_US').format(now),
end: DateFormat('yyyy-MM-dd', 'en_US').format(end),
);
if (!respBills.isSuccessful || respBills.body == null) {
if (context.mounted) {
throw Exception(
S
.of(context)
.errorAPIInvalidResponse(respBills.error?.toString() ?? ""),
);
} else {
throw Exception(
"[nocontext] Invalid API response: ${respBills.error}",
);
}
}
debugPrint(end.toIso8601String());
return respBills.body!.data
.where((BillRead e) => (e.attributes.nextExpectedMatch ??
DateTime.fromMicrosecondsSinceEpoch(0))
.toLocal()
.clearTime()
.isBefore(end.copyWith(day: end.day + 1)))
.toList(growable: false);
}

Future<bool> _fetchBalance() async {
/*if (lastMonthsEarned.isNotEmpty) { // :DEBUG:
if (lastMonthsEarned.isNotEmpty) {
// :DEBUG:
return true;
}*/
}

final FireflyIiiV2 apiV2 = context.read<FireflyService>().apiV2;
final DateTime now = DateTime.now().toLocal().clearTime();
Expand Down Expand Up @@ -994,6 +1032,7 @@ class _HomeMainState extends State<HomeMain>
}
return Card(
clipBehavior: Clip.hardEdge,
margin: const EdgeInsets.fromLTRB(4, 4, 4, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expand All @@ -1013,6 +1052,52 @@ class _HomeMainState extends State<HomeMain>
);
} else if (snapshot.hasError) {
return Text(snapshot.error!.toString());
} else {
return const Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.only(bottom: 8),
child: Padding(
padding: EdgeInsets.fromLTRB(4, 4, 4, 12),
child: Center(
child: CircularProgressIndicator(),
),
),
);
}
},
),
),
// No sizedbox, done via margins on (maybe hidden) budget card view
AnimatedHeight(
child: FutureBuilder<List<BillRead>>(
future: _fetchBills(),
builder: (BuildContext context,
AsyncSnapshot<List<BillRead>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
if (snapshot.data!.isEmpty) {
return const SizedBox.shrink();
}
return Card(
clipBehavior: Clip.hardEdge,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12),
child: Text(
"Bills for the next week",
style: Theme.of(context).textTheme.titleMedium,
),
),
BillList(
snapshot: snapshot,
),
],
),
);
} else if (snapshot.hasError) {
return Text(snapshot.error!.toString());
} else {
return const Card(
clipBehavior: Clip.hardEdge,
Expand Down Expand Up @@ -1149,6 +1234,111 @@ class BudgetList extends StatelessWidget {
}
}

class BillList extends StatelessWidget {
const BillList({
super.key,
required this.snapshot,
});

final AsyncSnapshot<List<BillRead>> snapshot;

@override
Widget build(BuildContext context) {
return SizedBox(
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final List<Widget> widgets = <Widget>[];
snapshot.data!.sort((BillRead a, BillRead b) {
final int dateCompare = (a.attributes.nextExpectedMatch ??
DateTime.now())
.compareTo(b.attributes.nextExpectedMatch ?? DateTime.now());
if (dateCompare != 0) {
return dateCompare;
}
final int orderCompare =
(a.attributes.order ?? 0).compareTo(b.attributes.order ?? 0);
if (orderCompare != 0) {
return orderCompare;
}
return a.attributes
.avgAmount()
.compareTo(b.attributes.avgAmount());
});

DateTime lastDate =
(snapshot.data!.first.attributes.nextExpectedMatch ??
DateTime.now())
.subtract(const Duration(days: 1));
for (BillRead bill in snapshot.data!) {
if (!(bill.attributes.active ?? false)) {
continue;
}

final DateTime nextMatch =
bill.attributes.nextExpectedMatch?.toLocal() ??
DateTime.now();
debugPrint(nextMatch.toIso8601String());
debugPrint(bill.attributes.nextExpectedMatchDiff);
final CurrencyRead currency = CurrencyRead(
id: bill.attributes.currencyId ?? "0",
type: "currencies",
attributes: Currency(
code: bill.attributes.currencyCode ?? "",
name: "",
symbol: bill.attributes.currencySymbol ?? "",
decimalPlaces: bill.attributes.currencyDecimalPlaces,
),
);

if (nextMatch != lastDate) {
if (widgets.isNotEmpty) {
widgets.add(const SizedBox(height: 8));
}
widgets.add(
RichText(
text: TextSpan(
text: DateFormat.yMd().format(nextMatch),
style: Theme.of(context).textTheme.titleMedium,
),
),
);
lastDate = nextMatch;
}
widgets.add(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
bill.attributes.name,
style: Theme.of(context).textTheme.titleSmall,
),
Text(
currency.fmt(bill.attributes.avgAmount()),
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontFeatures: <FontFeature>[
FontFeature.tabularFigures()
],
),
),
],
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: widgets,
);
},
),
),
);
}
}

class ChartCard extends StatelessWidget {
const ChartCard({
super.key,
Expand Down

0 comments on commit bf77d91

Please sign in to comment.