Fix the template page

This commit is contained in:
PapaTutuWawa 2025-05-10 22:33:20 +02:00
parent 60bfd9481f
commit 058291fa80
7 changed files with 211 additions and 80 deletions

View File

@ -145,22 +145,25 @@ Stream<void> watchTransactionTemplates(Account account) {
.transactionTemplates .transactionTemplates
.filter() .filter()
.account((q) => q.idEqualTo(account.id)) .account((q) => q.idEqualTo(account.id))
.recurringEqualTo(false)
.watchLazy(fireImmediately: true); .watchLazy(fireImmediately: true);
} }
Future<List<TransactionTemplate>> getTransactionTemplates(Account? account) { Future<List<TransactionTemplate>> getTransactionTemplates(
Account? account,
) async {
if (account == null) { if (account == null) {
return Future.value([]); return Future.value([]);
} }
return GetIt.I final a =
.get<Isar>() await GetIt.I
.transactionTemplates .get<Isar>()
.filter() .transactionTemplates
.account((q) => q.idEqualTo(account.id)) .filter()
.recurringEqualTo(false) .account((q) => q.idEqualTo(account.id))
.findAll(); .findAll();
return a;
} }
Stream<void> watchTransactions(Account account) { Stream<void> watchTransactions(Account account) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.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/state/core.dart';
import 'package:okane/ui/utils.dart'; import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_template.dart'; import 'package:okane/ui/widgets/add_template.dart';
@ -18,29 +19,45 @@ class TemplateListState extends State<TemplateListPage> {
return BlocBuilder<CoreCubit, CoreState>( return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) { builder: (context, state) {
final account = GetIt.I.get<CoreCubit>().activeAccount; final account = GetIt.I.get<CoreCubit>().activeAccount;
final nonRecurringTemplates =
state.transactionTemplates.where((t) => !t.recurring).toList();
return Stack( return Stack(
children: [ children: [
Column( CustomScrollView(
children: [ slivers: [
Padding( SliverToBoxAdapter(child: Text("Non-recurring")),
padding: EdgeInsets.only(top: 16), SliverList.builder(
child: ListView.builder( itemCount: nonRecurringTemplates.length,
itemCount: state.recurringTransactions.length, itemBuilder: (context, index) {
shrinkWrap: true, final template = nonRecurringTemplates[index];
itemBuilder: return ListTile(title: Text(template.name));
(ctx, idx) => ListTile( },
title: Text( ),
state SliverToBoxAdapter(child: Text("Recurring")),
.recurringTransactions[idx] SliverList.builder(
.template itemCount: state.recurringTransactions.length,
.value! itemBuilder: (context, index) {
.name, 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( Positioned(
right: 16, right: 16,
bottom: 16, bottom: 16,

View File

@ -181,14 +181,16 @@ class TransactionDetailsPage extends StatelessWidget {
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 8), padding: EdgeInsets.symmetric(vertical: 8),
child: Row( child: Row(
children: [ children: [
state.activeTransaction!.amount > 0 state.activeTransaction!.amount > 0
? Icon(Icons.add) ? Icon(Icons.add)
: Icon(Icons.remove), : Icon(Icons.remove),
Text(formatCurrency(state.activeTransaction!.amount)), Text(
], formatCurrency(state.activeTransaction!.amount),
), ),
],
),
), ),
], ],
), ),

View File

@ -76,6 +76,7 @@ class CoreCubit extends Cubit<CoreState> {
_transactionTemplatesStreamSubcription = watchTransactionTemplates( _transactionTemplatesStreamSubcription = watchTransactionTemplates(
activeAccount!, activeAccount!,
).listen((_) async { ).listen((_) async {
print("UPDATE");
emit( emit(
state.copyWith( state.copyWith(
transactionTemplates: await getTransactionTemplates(activeAccount!), transactionTemplates: await getTransactionTemplates(activeAccount!),

View File

@ -49,34 +49,32 @@ Future<T?> showDialogOrModal<T>({
Future<TransactionTemplate?> selectTransactionTemplate(BuildContext context) { Future<TransactionTemplate?> selectTransactionTemplate(BuildContext context) {
return showDialogOrModal<TransactionTemplate>( return showDialogOrModal<TransactionTemplate>(
context: context, context: context,
builder: (context) { builder: (context) {
return BlocBuilder<CoreCubit, CoreState>( return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) { builder: (context, state) {
if (state.transactionTemplates.isEmpty) { if (state.transactionTemplates.isEmpty) {
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: Text("No templates defined"), 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]);
},
),
); );
}, }
);
}, 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]);
},
),
);
},
);
},
); );
} }

View File

@ -214,7 +214,7 @@ class _AddRecurringTransactionTemplateWidgetState
decimal: false, decimal: false,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Amount", hintText: "Amount",
icon: Icon(Icons.euro), icon: Icon(Icons.euro),
), ),
), ),

View File

@ -4,6 +4,7 @@ import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart'; import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart'; import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.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/collections/template.dart';
import 'package:okane/database/database.dart'; import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.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:okane/ui/widgets/add_expense_category.dart';
import 'package:searchfield/searchfield.dart'; import 'package:searchfield/searchfield.dart';
enum Period { days, weeks, months, years }
class AddTransactionTemplateWidget extends StatefulWidget { class AddTransactionTemplateWidget extends StatefulWidget {
final VoidCallback onAdd; final VoidCallback onAdd;
@ -41,6 +44,11 @@ class _AddTransactionTemplateWidgetState
ExpenseCategory? _expenseCategory; ExpenseCategory? _expenseCategory;
bool _isRecurring = false;
Period _selectedPeriod = Period.weeks;
int _periodSize = 1;
String getBeneficiaryName(Beneficiary item) { String getBeneficiaryName(Beneficiary item) {
return switch (item.type) { return switch (item.type) {
BeneficiaryType.account => "${item.name} (Account)", BeneficiaryType.account => "${item.name} (Account)",
@ -51,6 +59,7 @@ class _AddTransactionTemplateWidgetState
Future<void> _submit(BuildContext context) async { Future<void> _submit(BuildContext context) async {
final beneficiaryName = _beneficiaryTextController.text; final beneficiaryName = _beneficiaryTextController.text;
if (_selectedBeneficiary == null && beneficiaryName.isEmpty) { if (_selectedBeneficiary == null && beneficiaryName.isEmpty) {
print("No beneficiary");
return; return;
} }
if (_templateNameController.text.isEmpty) { if (_templateNameController.text.isEmpty) {
@ -103,15 +112,31 @@ class _AddTransactionTemplateWidgetState
TransactionDirection.receive => 1, TransactionDirection.receive => 1,
}; };
final amount = factor * double.parse(_amountTextController.text).abs(); final amount = factor * double.parse(_amountTextController.text).abs();
final transaction = final template =
TransactionTemplate() TransactionTemplate()
..name = _templateNameController.text ..name = _templateNameController.text
..account.value = widget.activeAccountItem ..account.value = widget.activeAccountItem
..beneficiary.value = beneficiary ..beneficiary.value = beneficiary
..expenseCategory.value = _expenseCategory ..expenseCategory.value = _expenseCategory
..recurring = false ..recurring = _isRecurring
..amount = amount; ..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(); widget.onAdd();
} }
@ -197,8 +222,8 @@ class _AddTransactionTemplateWidgetState
decimal: false, decimal: false,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Amount", hintText: "Amount",
icon: Icon(Icons.euro), icon: Icon(Icons.euro),
), ),
), ),
), ),
@ -209,20 +234,105 @@ class _AddTransactionTemplateWidgetState
Padding( Padding(
padding: EdgeInsets.only(left: 16), padding: EdgeInsets.only(left: 16),
child: OutlinedButton( child: OutlinedButton(
onPressed: () async { onPressed: () async {
final category = await showDialogOrModal( final category = await showDialogOrModal(
context: context, context: context,
builder: (_) => AddExpenseCategory(), builder: (_) => AddExpenseCategory(),
); );
if (category == null) { if (category == null) {
return; return;
} }
setState(() => _expenseCategory = category); setState(() => _expenseCategory = category);
}, },
child: Text(_expenseCategory?.name ?? "None"), 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<Period>(
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: <Period>{_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,
), ),
], ],
), ),