diff --git a/lib/database/database.dart b/lib/database/database.dart index bc5cc36..4ae01b5 100644 --- a/lib/database/database.dart +++ b/lib/database/database.dart @@ -145,22 +145,25 @@ Stream watchTransactionTemplates(Account account) { .transactionTemplates .filter() .account((q) => q.idEqualTo(account.id)) - .recurringEqualTo(false) .watchLazy(fireImmediately: true); } -Future> getTransactionTemplates(Account? account) { +Future> getTransactionTemplates( + Account? account, +) async { if (account == null) { return Future.value([]); } - return GetIt.I - .get() - .transactionTemplates - .filter() - .account((q) => q.idEqualTo(account.id)) - .recurringEqualTo(false) - .findAll(); + final a = + await GetIt.I + .get() + .transactionTemplates + .filter() + .account((q) => q.idEqualTo(account.id)) + .findAll(); + + return a; } Stream watchTransactions(Account account) { diff --git a/lib/ui/pages/template_list.dart b/lib/ui/pages/template_list.dart index 72948fc..38376bf 100644 --- a/lib/ui/pages/template_list.dart +++ b/lib/ui/pages/template_list.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; +import 'package:grouped_list/grouped_list.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/add_template.dart'; @@ -18,29 +19,45 @@ class TemplateListState extends State { return BlocBuilder( builder: (context, state) { final account = GetIt.I.get().activeAccount; + final nonRecurringTemplates = + state.transactionTemplates.where((t) => !t.recurring).toList(); return Stack( children: [ - Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 16), - child: ListView.builder( - itemCount: state.recurringTransactions.length, - shrinkWrap: true, - itemBuilder: - (ctx, idx) => ListTile( - title: Text( - state - .recurringTransactions[idx] - .template - .value! - .name, - ), - ), - ), + CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: Text("Non-recurring")), + SliverList.builder( + itemCount: nonRecurringTemplates.length, + itemBuilder: (context, index) { + final template = nonRecurringTemplates[index]; + return ListTile(title: Text(template.name)); + }, + ), + SliverToBoxAdapter(child: Text("Recurring")), + SliverList.builder( + itemCount: state.recurringTransactions.length, + itemBuilder: (context, index) { + final template = state.recurringTransactions[index]; + return ListTile(title: Text(template.template.value!.name)); + }, ), ], ), + /*Padding( + padding: EdgeInsets.only(top: 16), + child: ListView.builder( + itemCount: state.recurringTransactions.length, + shrinkWrap: true, + itemBuilder: (ctx, idx) { + print(idx); + return ListTile( + title: Text( + state.recurringTransactions[idx].template.value!.name, + ), + ); + }, + ), + ),*/ Positioned( right: 16, bottom: 16, diff --git a/lib/ui/pages/transaction_details.dart b/lib/ui/pages/transaction_details.dart index 63b0eed..ac37e52 100644 --- a/lib/ui/pages/transaction_details.dart +++ b/lib/ui/pages/transaction_details.dart @@ -181,14 +181,16 @@ class TransactionDetailsPage extends StatelessWidget { Padding( padding: EdgeInsets.symmetric(vertical: 8), - child: Row( - children: [ - state.activeTransaction!.amount > 0 - ? Icon(Icons.add) - : Icon(Icons.remove), - Text(formatCurrency(state.activeTransaction!.amount)), - ], - ), + child: Row( + children: [ + state.activeTransaction!.amount > 0 + ? Icon(Icons.add) + : Icon(Icons.remove), + Text( + formatCurrency(state.activeTransaction!.amount), + ), + ], + ), ), ], ), diff --git a/lib/ui/state/core.dart b/lib/ui/state/core.dart index 45b0ebf..1a1a33d 100644 --- a/lib/ui/state/core.dart +++ b/lib/ui/state/core.dart @@ -76,6 +76,7 @@ class CoreCubit extends Cubit { _transactionTemplatesStreamSubcription = watchTransactionTemplates( activeAccount!, ).listen((_) async { + print("UPDATE"); emit( state.copyWith( transactionTemplates: await getTransactionTemplates(activeAccount!), diff --git a/lib/ui/utils.dart b/lib/ui/utils.dart index 927de3a..80cc5b8 100644 --- a/lib/ui/utils.dart +++ b/lib/ui/utils.dart @@ -49,34 +49,32 @@ Future showDialogOrModal({ Future selectTransactionTemplate(BuildContext context) { return showDialogOrModal( - context: context, - builder: (context) { - return BlocBuilder( - builder: (context, state) { - if (state.transactionTemplates.isEmpty) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: Text("No templates defined"), - ); - } - - return ListView.builder( - itemCount: state.transactionTemplates.length, - itemBuilder: - (context, index) => ListTile( - title: Text( - state.transactionTemplates[index].name, - ), - onTap: () { - Navigator.of( - context, - ).pop(state.transactionTemplates[index]); - }, - ), + context: context, + builder: (context) { + return BlocBuilder( + builder: (context, state) { + if (state.transactionTemplates.isEmpty) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text("No templates defined"), ); - }, - ); - }, + } + + return ListView.builder( + itemCount: state.transactionTemplates.length, + itemBuilder: + (context, index) => ListTile( + title: Text(state.transactionTemplates[index].name), + onTap: () { + Navigator.of( + context, + ).pop(state.transactionTemplates[index]); + }, + ), + ); + }, + ); + }, ); } diff --git a/lib/ui/widgets/add_recurring_transaction.dart b/lib/ui/widgets/add_recurring_transaction.dart index 7bd0a6d..f178ed3 100644 --- a/lib/ui/widgets/add_recurring_transaction.dart +++ b/lib/ui/widgets/add_recurring_transaction.dart @@ -214,7 +214,7 @@ class _AddRecurringTransactionTemplateWidgetState decimal: false, ), decoration: InputDecoration( - hintText: "Amount", + hintText: "Amount", icon: Icon(Icons.euro), ), ), diff --git a/lib/ui/widgets/add_template.dart b/lib/ui/widgets/add_template.dart index 5a50634..b0baa82 100644 --- a/lib/ui/widgets/add_template.dart +++ b/lib/ui/widgets/add_template.dart @@ -4,6 +4,7 @@ import 'package:get_it/get_it.dart'; import 'package:okane/database/collections/account.dart'; import 'package:okane/database/collections/beneficiary.dart'; import 'package:okane/database/collections/expense_category.dart'; +import 'package:okane/database/collections/recurrent.dart'; import 'package:okane/database/collections/template.dart'; import 'package:okane/database/database.dart'; import 'package:okane/ui/state/core.dart'; @@ -12,6 +13,8 @@ import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/add_expense_category.dart'; import 'package:searchfield/searchfield.dart'; +enum Period { days, weeks, months, years } + class AddTransactionTemplateWidget extends StatefulWidget { final VoidCallback onAdd; @@ -41,6 +44,11 @@ class _AddTransactionTemplateWidgetState ExpenseCategory? _expenseCategory; + bool _isRecurring = false; + + Period _selectedPeriod = Period.weeks; + int _periodSize = 1; + String getBeneficiaryName(Beneficiary item) { return switch (item.type) { BeneficiaryType.account => "${item.name} (Account)", @@ -51,6 +59,7 @@ class _AddTransactionTemplateWidgetState Future _submit(BuildContext context) async { final beneficiaryName = _beneficiaryTextController.text; if (_selectedBeneficiary == null && beneficiaryName.isEmpty) { + print("No beneficiary"); return; } if (_templateNameController.text.isEmpty) { @@ -103,15 +112,31 @@ class _AddTransactionTemplateWidgetState TransactionDirection.receive => 1, }; final amount = factor * double.parse(_amountTextController.text).abs(); - final transaction = + final template = TransactionTemplate() ..name = _templateNameController.text ..account.value = widget.activeAccountItem ..beneficiary.value = beneficiary ..expenseCategory.value = _expenseCategory - ..recurring = false + ..recurring = _isRecurring ..amount = amount; - await upsertTransactionTemplate(transaction); + await upsertTransactionTemplate(template); + + if (_isRecurring) { + final days = switch (_selectedPeriod) { + Period.days => _periodSize, + Period.weeks => _periodSize * 7, + Period.months => _periodSize * 31, + Period.years => _periodSize * 365, + }; + final recurringTransaction = + RecurringTransaction() + ..account.value = widget.activeAccountItem + ..template.value = template + ..lastExecution = null + ..days = days; + await upsertRecurringTransaction(recurringTransaction); + } widget.onAdd(); } @@ -197,8 +222,8 @@ class _AddTransactionTemplateWidgetState decimal: false, ), decoration: InputDecoration( - hintText: "Amount", - icon: Icon(Icons.euro), + hintText: "Amount", + icon: Icon(Icons.euro), ), ), ), @@ -209,20 +234,105 @@ class _AddTransactionTemplateWidgetState Padding( padding: EdgeInsets.only(left: 16), - child: OutlinedButton( - onPressed: () async { - final category = await showDialogOrModal( - context: context, - builder: (_) => AddExpenseCategory(), - ); - if (category == null) { - return; - } + child: OutlinedButton( + onPressed: () async { + final category = await showDialogOrModal( + context: context, + builder: (_) => AddExpenseCategory(), + ); + if (category == null) { + return; + } - setState(() => _expenseCategory = category); - }, - child: Text(_expenseCategory?.name ?? "None"), + setState(() => _expenseCategory = category); + }, + child: Text(_expenseCategory?.name ?? "None"), + ), ), + ], + ), + + Row( + children: [ + Text("Is recurring"), + Padding( + padding: EdgeInsets.only(left: 16), + child: Switch( + value: _isRecurring, + onChanged: (value) { + setState(() { + _isRecurring = value; + }); + }, + ), + ), + ], + ), + Padding( + padding: EdgeInsets.only(left: 16, right: 16, top: 16), + child: SegmentedButton( + segments: [ + ButtonSegment(value: Period.days, label: Text("Days")), + ButtonSegment(value: Period.weeks, label: Text("Weeks")), + ButtonSegment(value: Period.months, label: Text("Months")), + ButtonSegment(value: Period.years, label: Text("Years")), + ], + selected: {_selectedPeriod}, + multiSelectionEnabled: false, + onSelectionChanged: + _isRecurring + ? (selection) { + setState(() => _selectedPeriod = selection.first); + } + : null, + ), + ), + + Row( + children: [ + IconButton( + icon: Icon(Icons.remove), + onPressed: + _isRecurring + ? () { + if (_periodSize <= 1) { + return; + } + + setState(() { + _periodSize--; + }); + } + : null, + ), + + SizedBox( + width: 100, + child: Center( + child: Text( + switch (_selectedPeriod) { + Period.days => "$_periodSize days", + Period.weeks => "$_periodSize weeks", + Period.months => "$_periodSize months", + Period.years => "$_periodSize years", + }, + style: TextStyle( + color: _isRecurring ? Colors.black : Colors.grey, + ), + ), + ), + ), + + IconButton( + icon: Icon(Icons.add), + onPressed: + _isRecurring + ? () { + setState(() { + _periodSize++; + }); + } + : null, ), ], ),