okane/lib/ui/widgets/add_transaction.dart

340 lines
11 KiB
Dart

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<AddTransactionWidget> createState() => _AddTransactionWidgetState();
}
class _AddTransactionWidgetState extends State<AddTransactionWidget> {
final TextEditingController _beneficiaryTextController =
TextEditingController();
final TextEditingController _amountTextController = TextEditingController();
DateTime _selectedDate = DateTime.now();
SearchFieldListItem<Beneficiary>? _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<void> _submit(BuildContext context) async {
final beneficiaryName = _beneficiaryTextController.text;
if (_selectedBeneficiary == null && beneficiaryName.isEmpty) {
return;
}
final db = GetIt.I.get<OkaneDatabase>();
Beneficiary? beneficiary = _selectedBeneficiary?.item;
if (beneficiary == null ||
getBeneficiaryName(beneficiary) != beneficiaryName) {
// Add a new beneficiary, if none was selected
final result = await showDialog<bool>(
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<TransactionDirection>(
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: <TransactionDirection>{_selectedDirection},
multiSelectionEnabled: false,
onSelectionChanged: (selection) {
setState(() => _selectedDirection = selection.first);
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => SearchField<Beneficiary>(
suggestions:
state.beneficiaries
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
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),
),
),
],
),
);
}
}