import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; 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/transaction.dart'; import 'package:okane/database/database.dart'; import 'package:okane/ui/state/core.dart'; import 'package:okane/ui/transaction.dart'; import 'package:okane/ui/utils.dart'; import 'package:okane/ui/widgets/add_expense_category.dart'; import 'package:searchfield/searchfield.dart'; class AddTransactionWidget extends StatefulWidget { final VoidCallback onAdd; final Account activeAccountItem; const AddTransactionWidget({ super.key, required this.activeAccountItem, required this.onAdd, }); @override State createState() => _AddTransactionWidgetState(); } class _AddTransactionWidgetState extends State { final TextEditingController _beneficiaryTextController = TextEditingController(); final TextEditingController _amountTextController = TextEditingController(); DateTime _selectedDate = DateTime.now(); SearchFieldListItem? _selectedBeneficiary; TransactionDirection _selectedDirection = TransactionDirection.send; ExpenseCategory? _expenseCategory = null; String getBeneficiaryName(Beneficiary item) { return switch (item.type) { BeneficiaryType.account => "${item.name} (Account)", BeneficiaryType.other => item.name, }; } Future _submit(BuildContext context) async { final beneficiaryName = _beneficiaryTextController.text; if (_selectedBeneficiary == null && beneficiaryName.isEmpty) { return; } Beneficiary? beneficiary = _selectedBeneficiary?.item; if (beneficiary == null || getBeneficiaryName(beneficiary) != beneficiaryName) { // Add a new beneficiary, if none was selected final result = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text("Add Beneficiary"), content: Text( "The beneficiary '$beneficiaryName' does not exist. Do you want to add it?", ), actions: [ TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: const Text('Add'), onPressed: () => Navigator.of(context).pop(true), ), TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: const Text('Cancel'), onPressed: () => Navigator.of(context).pop(false), ), ], ), ); if (result == null || !result) { return; } beneficiary = Beneficiary() ..name = beneficiaryName ..type = BeneficiaryType.other; await upsertBeneficiary(beneficiary); } final factor = switch (_selectedDirection) { TransactionDirection.send => -1, TransactionDirection.receive => 1, }; final amount = factor * double.parse(_amountTextController.text).abs(); final transaction = Transaction() ..account.value = widget.activeAccountItem ..beneficiary.value = beneficiary ..amount = amount ..tags = [] ..expenseCategory.value = _expenseCategory ..date = _selectedDate; await upsertTransaction(transaction); if (beneficiary.type == BeneficiaryType.account) { final otherTransaction = Transaction() ..account.value = beneficiary.account.value! ..beneficiary.value = await getAccountBeneficiary( widget.activeAccountItem, ) ..date = _selectedDate ..expenseCategory.value = _expenseCategory ..amount = -1 * amount; await upsertTransaction(otherTransaction); } widget.onAdd(); } @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: ListView( shrinkWrap: true, children: [ OutlinedButton( onPressed: () async { final template = await showDialogOrModal( context: context, builder: (context) { return BlocBuilder( builder: (context, state) { if (state.transactionTemplates.isEmpty) { return 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]); }, ), ); }, ); }, ); if (template == null) { return; } _amountTextController.text = template.amount.toString(); _selectedDirection = template.amount > 0 ? TransactionDirection.receive : TransactionDirection.send; _selectedBeneficiary = SearchFieldListItem( getBeneficiaryName(template.beneficiary.value!), item: template.beneficiary.value!, ); _beneficiaryTextController.text = getBeneficiaryName( template.beneficiary.value!, ); }, child: Text("Use template"), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: BlocBuilder( builder: (context, state) => SearchField( suggestions: state.beneficiaries .where((el) { final bloc = GetIt.I.get(); if (el.type == BeneficiaryType.account) { return el.account.value?.id.toInt() == bloc.activeAccount?.id.toInt(); } return true; }) .map((el) { return SearchFieldListItem( getBeneficiaryName(el), item: el, ); }) .toList(), hint: "Beneficiary", controller: _beneficiaryTextController, selectedValue: _selectedBeneficiary, onSuggestionTap: (beneficiary) { setState(() => _selectedBeneficiary = beneficiary); }, ), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: TextField( controller: _amountTextController, keyboardType: TextInputType.numberWithOptions( signed: false, decimal: false, ), decoration: InputDecoration(hintText: "Amount"), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text("Date"), OutlinedButton( onPressed: () async { final dt = await showDatePicker( context: context, initialDate: _selectedDate, firstDate: DateTime(1), lastDate: DateTime(9999), ); if (dt == null) return; setState(() => _selectedDate = dt); }, child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, children: [ Icon(Icons.date_range), Text(formatDateTime(_selectedDate)), ], ), ), ], ), ), Row( children: [ Text("Expense category"), OutlinedButton( onPressed: () async { final category = await showDialogOrModal( context: context, builder: (_) => AddExpenseCategory(), ); if (category == null) { return; } setState(() => _expenseCategory = category); }, child: Text(_expenseCategory?.name ?? "None"), ), ], ), Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: SegmentedButton( segments: [ ButtonSegment( value: TransactionDirection.send, label: Text("Send"), icon: Icon(Icons.remove), ), ButtonSegment( value: TransactionDirection.receive, label: Text("Receive"), icon: Icon(Icons.add), ), ], selected: {_selectedDirection}, multiSelectionEnabled: false, onSelectionChanged: (selection) { setState(() => _selectedDirection = selection.first); }, ), ), Align( alignment: Alignment.centerRight, child: OutlinedButton( onPressed: () => _submit(context), child: Text("Add"), ), ), ], ), ); } }