Skip to content

Commit

Permalink
Merge pull request #122 from flow-mn/sadespresso/issue57
Browse files Browse the repository at this point in the history
Upcoming transactions
  • Loading branch information
sadespresso committed Mar 30, 2024
2 parents 7a8f417 + 90bf02c commit 32f1916
Show file tree
Hide file tree
Showing 15 changed files with 406 additions and 31 deletions.
7 changes: 7 additions & 0 deletions assets/l10n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"account": "Account",
"account.name": "Account name",
"account.balance": "Balance",
"account.balance.upcomingDescription": "Upcoming transactions don't affect current balance",
"account.excludeFromTotalBalance": "Exclude from balance",
"account.excludeFromTotalBalance.description": "If you check this option, this account's balance will not be included in the total balance. Useful for savings or non-personal accounts.",
"account.updateBalance": "Update balance",
Expand All @@ -71,6 +72,7 @@
"account.edit": "Edit account",
"account.edit.selectCurrency": "Select a currency",
"account.transactions": "Transactions",
"account.transactions.title": "\"{account}\" transactions",
"account.delete": "Delete account",
"account.delete.warning": "Deleting this account will also delete {transactionCount} transactions associated. This action is irreversible!",
"account.noAccounts": "You don't have any accounts!",
Expand All @@ -93,6 +95,9 @@
"transaction.transfer.to.title": "To {account}",
"transaction.transfer.fromToTitle": "From {from} to {to}",

"transactions.all": "All transactions",
"transactions.upcoming": "Upcoming transactions",

"category": "Category",
"category.name": "Category name",
"category.new": "Add a category",
Expand Down Expand Up @@ -133,6 +138,8 @@
"tabs.home.noTransactions.allTime": "You don't have any transactions",
"tabs.home.noTransactions.last7Days": "No transactions for the last 7 days",
"tabs.home.noTransactions.addSome": "Click on (+) button below to add a new transaction",
"tabs.home.upcomingTransactions": "Upcoming ({count})",
"tabs.home.upcomingTransactions.seeAll": "See all",
"tabs.home.transactionsCount": "{count} transactions",
"tabs.home.last7days": "Last 7 days",
"tabs.home.totalBalance": "Total balance",
Expand Down
7 changes: 7 additions & 0 deletions assets/l10n/mn_MN.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"account": "Данс",
"account.name": "Дансны нэр",
"account.balance": "Үлдэгдэл",
"account.balance.upcomingDescription": "Төлөвлөсөн гүйлгээнүүд одоогийн дансны үлдэгдэлд нөлөөлөхгүй",
"account.excludeFromTotalBalance": "Нийт үлдэгдэлд тооцохгүй",
"account.excludeFromTotalBalance.description": "Энэ сонголтыг идэвхжүүлбэл энэ дансны үлдэгдэл нийт үлдэгдэлд бодогдохгүй. Хадгаламж, хувийн бус данс гэх мэт зүйлсд тохиромжтой.",
"account.updateBalance": "Үлдэгдэл өөрчлөх",
Expand All @@ -71,6 +72,7 @@
"account.edit": "Данс засварлах",
"account.edit.selectCurrency": "Валют сонгох",
"account.transactions": "Гүйлгээнүүд",
"account.transactions.title": "\"{account}\"-н гүйлгээнүүд",
"account.delete": "Дансыг устгах",
"account.delete.warning": "Энэ дансыг устгавал холбоотой {transactionCount} гүйлгээг хамт устгах болно. Энэ үйлдлийг буцаах боломжгүй юм!",
"account.noAccounts": "Танд үүсгэсэн данс алга байна!",
Expand All @@ -93,6 +95,9 @@
"transaction.transfer.to.title": "{account}-руу",
"transaction.transfer.fromToTitle": "{from}-с {to} руу",

"transactions.all": "Бүх гүйлгээнүүд",
"transactions.upcoming": "Төлөвлөсөн гүйлгээнүүд",

"category": "Ангилал",
"category.name": "Нэр",
"category.new": "Ангилал үүсгэх",
Expand Down Expand Up @@ -133,6 +138,8 @@
"tabs.home.noTransactions.allTime": "Танд одоогоор гүйлгээ алга байна",
"tabs.home.noTransactions.last7Days": "Сүүлийн долоо хоногт хийгдсэн гүйлгээ алга байна",
"tabs.home.noTransactions.addSome": "Доор байрлах (+) товч дээр дарж гүйлгээ нэмээрэй",
"tabs.home.upcomingTransactions": "Төлөвлөсөн ({count})",
"tabs.home.upcomingTransactions.seeAll": "Бүгд",
"tabs.home.transactionsCount": "{count} гүйлгээ",
"tabs.home.last7days": "Сүүлийн 7 хоног",
"tabs.home.totalBalance": "Нийт үлдэгдэл",
Expand Down
11 changes: 7 additions & 4 deletions lib/entity/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flow/entity/_base.dart';
import 'package:flow/entity/transaction.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:moment_dart/moment_dart.dart';
import 'package:objectbox/objectbox.dart';
import 'package:uuid/uuid.dart';

Expand Down Expand Up @@ -55,10 +56,12 @@ class Account implements EntityBase {
@Transient()
@JsonKey(includeFromJson: false, includeToJson: false)
double get balance {
return transactions.fold<double>(
0,
(previousValue, element) => previousValue + element.amount,
);
return transactions
.where((element) => element.transactionDate.isPast)
.fold<double>(
0,
(previousValue, element) => previousValue + element.amount,
);
}

Account({
Expand Down
10 changes: 8 additions & 2 deletions lib/objectbox/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,17 @@ extension TransactionListActions on Iterable<Transaction> {
expenses.fold(0, (value, element) => value + element.amount);
double get sum => fold(0, (value, element) => value + element.amount);

Map<DateTime, List<Transaction>> groupByDate() {
/// If [mergeFutureTransactions] is set to true, transactions in future
/// relative to [anchor] will be grouped into the same group
Map<DateTime, List<Transaction>> groupByDate({
DateTime? anchor,
}) {
anchor ??= DateTime.now();

final Map<DateTime, List<Transaction>> value = {};

for (final transaction in this) {
final date = transaction.transactionDate.toLocal().startOfDay();
final DateTime date = transaction.transactionDate.toLocal().startOfDay();

value[date] ??= [];
value[date]!.add(transaction);
Expand Down
22 changes: 22 additions & 0 deletions lib/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import 'package:flow/routes/utils/crop_square_image_page.dart';
import 'package:flow/sync/export/mode.dart';
import 'package:flow/sync/import/import_v1.dart';
import 'package:flow/utils/utils.dart';
import 'package:flow/widgets/general/info_text.dart';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';

final router = GoRouter(
Expand All @@ -52,6 +54,26 @@ final router = GoRouter(
transactionId: int.tryParse(state.pathParameters["id"]!) ?? -1,
),
),
GoRoute(
path: '/transactions',
builder: (context, state) => TransactionsPage.all(
title: "transactions.all".t(context),
),
),
GoRoute(
path: '/transactions/upcoming',
builder: (context, state) => TransactionsPage.upcoming(
title: "transactions.upcoming".t(context),
header: InfoText(
singleLine: true,
child: Text(
"account.balance.upcomingDescription".t(context),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
),
GoRoute(
path: '/account/new',
builder: (context, state) => const AccountPage.create(),
Expand Down
42 changes: 34 additions & 8 deletions lib/routes/home/home_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import 'package:flow/entity/transaction.dart';
import 'package:flow/objectbox.dart';
import 'package:flow/objectbox/actions.dart';
import 'package:flow/objectbox/objectbox.g.dart';
import 'package:flow/widgets/general/wavy_divider.dart';
import 'package:flow/widgets/home/home/no_transactions.dart';
import 'package:flow/widgets/home/greetings_bar.dart';
import 'package:flow/widgets/grouped_transaction_list.dart';
import 'package:flow/widgets/home/home/upcoming_transactions_list.dart';
import 'package:flow/widgets/home/transactions_date_header.dart';
import 'package:flutter/material.dart';
import 'package:moment_dart/moment_dart.dart';
Expand Down Expand Up @@ -47,10 +49,7 @@ class _HomeTabState extends State<HomeTab> with AutomaticKeepAliveClientMixin {
return StreamBuilder<Query<Transaction>>(
stream: qb().watch(triggerImmediately: true),
builder: (context, snapshot) {
final transactions = snapshot.data
?.find()
.where((element) => element.transactionDate <= Moment.now())
.toList();
final transactions = snapshot.data?.find();

return Column(
children: [
Expand Down Expand Up @@ -80,18 +79,45 @@ class _HomeTabState extends State<HomeTab> with AutomaticKeepAliveClientMixin {
}

Widget buildGroupedList(
BuildContext context, List<Transaction> transactions) {
final Map<DateTime, List<Transaction>> grouped = transactions.groupByDate();
BuildContext context,
List<Transaction> transactions,
) {
final Map<DateTime, List<Transaction>> grouped = transactions
.where((element) => element.transactionDate.isPast)
.groupByDate();

final List<Widget> headers = grouped.keys
.map((date) =>
TransactionListDateHeader(transactions: grouped[date]!, date: date))
.map(
(date) => TransactionListDateHeader(
transactions: grouped[date]!,
date: date,
),
)
.toList();

final List<Transaction> upcoming = transactions
.where((element) => element.transactionDate.isFuture)
.toList();

final Widget? header = upcoming.isEmpty
? null
: Column(
children: [
UpcomingTransactionsList(
transactions: upcoming,
shouldCombineTransferIfNeeded: true,
),
const SizedBox(height: 16.0),
const WavyDivider(),
],
);

return GroupedTransactionList(
controller: widget.scrollController,
transactions: grouped.values.toList(),
shouldCombineTransferIfNeeded: true,
headers: headers,
header: header,
listPadding: const EdgeInsets.only(
top: 0,
bottom: 80.0,
Expand Down
9 changes: 6 additions & 3 deletions lib/routes/transaction_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -481,10 +481,10 @@ class _TransactionPageState extends State<TransactionPage> {
}

void selectTransactionDate() async {
final result = await showDatePicker(
final DateTime? result = await showDatePicker(
context: context,
firstDate: DateTime.fromMicrosecondsSinceEpoch(0),
lastDate: DateTime.now().add(const Duration(days: 365)),
lastDate: DateTime(9999, 12, 31),
initialDate: _transactionDate,
);

Expand All @@ -494,7 +494,7 @@ class _TransactionPageState extends State<TransactionPage> {

if (!mounted || result == null) return;

final timeResult = await showTimePicker(
final TimeOfDay? timeResult = await showTimePicker(
context: context,
initialTime: TimeOfDay.fromDateTime(_transactionDate),
);
Expand Down Expand Up @@ -582,12 +582,15 @@ class _TransactionPageState extends State<TransactionPage> {
_selectedAccount!.transferTo(
targetAccount: _selectedAccountTransferTo!,
amount: _amount.abs(),
transactionDate: _transactionDate,
title: formattedTitle,
);
} else {
_selectedAccount!.createAndSaveTransaction(
amount: _amount,
title: formattedTitle,
category: _selectedCategory,
transactionDate: _transactionDate,
);
}

Expand Down
58 changes: 55 additions & 3 deletions lib/routes/transactions_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ class TransactionsPage extends StatefulWidget {
final QueryBuilder<Transaction> query;
final String? title;

const TransactionsPage({super.key, required this.query, this.title});
final Widget? header;

factory TransactionsPage.account(
{Key? key, required int accountId, String? title}) {
const TransactionsPage({
super.key,
required this.query,
this.title,
this.header,
});

factory TransactionsPage.account({
Key? key,
required int accountId,
String? title,
Widget? header,
}) {
final QueryBuilder<Transaction> queryBuilder = ObjectBox()
.box<Transaction>()
.query(Transaction_.account.equals(accountId))
Expand All @@ -24,6 +35,46 @@ class TransactionsPage extends StatefulWidget {
query: queryBuilder,
key: key,
title: title,
header: header,
);
}

factory TransactionsPage.all({
Key? key,
String? title,
Widget? header,
}) {
final QueryBuilder<Transaction> queryBuilder = ObjectBox()
.box<Transaction>()
.query()
.order(Transaction_.transactionDate, flags: Order.descending);

return TransactionsPage(
query: queryBuilder,
key: key,
title: title,
header: header,
);
}

factory TransactionsPage.upcoming({
Key? key,
DateTime? anchor,
String? title,
Widget? header,
}) {
anchor ??= DateTime.now();

final QueryBuilder<Transaction> queryBuilder = ObjectBox()
.box<Transaction>()
.query(Transaction_.transactionDate.greaterThanDate(anchor))
.order(Transaction_.transactionDate, flags: Order.descending);

return TransactionsPage(
query: queryBuilder,
key: key,
title: title,
header: header,
);
}

Expand Down Expand Up @@ -55,6 +106,7 @@ class _TransactionsPageState extends State<TransactionsPage> {
return GroupedTransactionList(
transactions: grouped.values.toList(),
headers: headers,
header: widget.header,
);
},
),
Expand Down
25 changes: 25 additions & 0 deletions lib/utils/extensions/iterables.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,29 @@ extension Iterables<E> on Iterable<E> {
}
return null;
}

/// Returns a list with items alternating from [this] and [other].
///
/// Both iterables must have the same length.
///
/// Example:
/// ```dart
/// List<Object> list1 = [1, 2, 3];
/// List<Object> list2 = ['a', 'b', 'c'];
/// list1.alternate(list2); // [1, 'a', 2, 'b', 3, 'c']
/// ```
List<E> alternate(Iterable<E> other) {
if (length != other.length) {
throw ArgumentError('Both iterables must have the same length');
}

List<E> result = [];

for (int i = 0; i < length; i++) {
result.add(elementAt(i));
result.add(other.elementAt(i));
}

return result;
}
}
2 changes: 1 addition & 1 deletion lib/widgets/account_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class AccountCard extends StatelessWidget {
),
CupertinoContextMenuAction(
onPressed: () => context.push(
"/account/${account.id}/transactions?title=${account.name}"),
"/account/${account.id}/transactions?title=${"account.transactions.title".t(context, account.name)}"),
isDefaultAction: true,
trailingIcon: CupertinoIcons.square_list,
child: Text("account.transactions".t(context)),
Expand Down
Loading

0 comments on commit 32f1916

Please sign in to comment.