import 'package:drift/drift.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:okane/database/sqlite.dart'; import 'package:okane/i18n/strings.g.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'; typedef AddTransactionCallback = void Function(TransactionDto); class AddTransactionWidget extends StatefulWidget { final AddTransactionCallback onAdd; final Account activeAccountItem; final TransactionTemplateDto? template; const AddTransactionWidget({ super.key, this.template, 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; @override void initState() { super.initState(); // TODO /* if (widget.template != null) { _selectedDirection = widget.template!.amount > 0 ? TransactionDirection.receive : TransactionDirection.send; _amountTextController.text = widget.template!.amount.toString(); _beneficiaryTextController.text = widget.template!; _selectedBeneficiary = SearchFieldListItem( getBeneficiaryName(widget.template!.beneficiary.value!), item: widget.template!.beneficiary.value!, ); }*/ } String getBeneficiaryName(Beneficiary item) { return switch (item.type) { BeneficiaryType.account => t.common.beneficiary.nameWithAccount( name: item.name, ), BeneficiaryType.other => item.name, }; } Future _submit(BuildContext context) async { final beneficiaryName = _beneficiaryTextController.text; if (_selectedBeneficiary == null && beneficiaryName.isEmpty) { return; } final db = GetIt.I.get(); 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: Text(t.common.beneficiary.addBeneficiary.title), content: Text( t.common.beneficiary.addBeneficiary.body(name: beneficiaryName), ), actions: [ TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: Text(t.modals.add), onPressed: () => Navigator.of(context).pop(true), ), TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: Text(t.modals.cancel), onPressed: () => Navigator.of(context).pop(false), ), ], ), ); if (result == null || !result) { return; } beneficiary = await db.beneficiariesDao.upsertBeneficiary( BeneficiariesCompanion( name: Value(beneficiaryName), type: Value(BeneficiaryType.other), ), ); } final factor = switch (_selectedDirection) { TransactionDirection.send => -1, TransactionDirection.receive => 1, }; final amount = factor * double.parse(_amountTextController.text).abs(); final rawTransaction = TransactionsCompanion( accountId: Value(widget.activeAccountItem.id), beneficiaryId: Value(beneficiary.id), amount: Value(amount), // tags: [], expenseCategoryId: Value(_expenseCategory?.id), date: Value(_selectedDate), ); final transaction = await db.transactionsDao.upsertTransaction( rawTransaction, ); if (beneficiary.type == BeneficiaryType.account) { final otherTransaction = rawTransaction.copyWith( accountId: Value(beneficiary.accountId!), beneficiaryId: Value( (await db.beneficiariesDao.getAccountBeneficiary( widget.activeAccountItem, )).id, ), amount: Value(-1 * rawTransaction.amount.value), ); await db.transactionsDao.upsertTransaction(otherTransaction); } widget.onAdd( TransactionDto( transaction: transaction, beneficiary: beneficiary, expenseCategory: _expenseCategory, ), ); } @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: ListView( shrinkWrap: true, children: [ OutlinedButton( onPressed: () async { final template = await selectTransactionTemplate(context); if (template == null) { return; } _amountTextController.text = template.template.amount.toString(); _selectedDirection = template.template.amount > 0 ? TransactionDirection.receive : TransactionDirection.send; _selectedBeneficiary = SearchFieldListItem( getBeneficiaryName(template.beneficiary), item: template.beneficiary, ); _beneficiaryTextController.text = getBeneficiaryName( template.beneficiary, ); }, child: Text(t.pages.transactions.addTransaction.useTemplate), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: SegmentedButton( segments: [ ButtonSegment( value: TransactionDirection.send, label: Text(t.common.transaction.directionSend), icon: Icon(Icons.remove), ), ButtonSegment( value: TransactionDirection.receive, label: Text(t.common.transaction.directionReceive), icon: Icon(Icons.add), ), ], selected: {_selectedDirection}, multiSelectionEnabled: false, onSelectionChanged: (selection) { setState(() => _selectedDirection = selection.first); }, ), ), 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.accountId == bloc.activeAccount?.id.toInt(); } return true; }) .map((el) { return SearchFieldListItem( getBeneficiaryName(el), item: el, ); }) .toList(), hint: switch (_selectedDirection) { TransactionDirection.send => t.common.transaction.beneficiaryTextfieldHintSend, TransactionDirection.receive => t.common.transaction.beneficiaryTextfieldHintReceive, }, 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: t.common.amount, icon: Icon(Icons.euro), ), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text(t.common.date), Padding( padding: EdgeInsets.only(left: 16), child: 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(t.common.expenseCategory.name), Padding( padding: EdgeInsets.only(left: 16), child: OutlinedButton( onPressed: () async { final category = await showDialogOrModal( context: context, builder: (_) => AddExpenseCategory(), ); if (category == null) { return; } setState(() => _expenseCategory = category); }, child: Text( _expenseCategory?.name ?? t.common.expenseCategory.none, ), ), ), ], ), Align( alignment: Alignment.centerRight, child: OutlinedButton( onPressed: () => _submit(context), child: Text(t.modals.add), ), ), ], ), ); } }