This commit is contained in:
PapaTutuWawa 2025-05-09 20:23:45 +02:00
parent 5a2dbf8962
commit 60bfd9481f
6 changed files with 234 additions and 190 deletions

View File

@ -28,8 +28,7 @@ class TemplateListState extends State<TemplateListPage> {
itemCount: state.recurringTransactions.length, itemCount: state.recurringTransactions.length,
shrinkWrap: true, shrinkWrap: true,
itemBuilder: itemBuilder:
(ctx, idx) => Card( (ctx, idx) => ListTile(
child: ListTile(
title: Text( title: Text(
state state
.recurringTransactions[idx] .recurringTransactions[idx]
@ -38,7 +37,6 @@ class TemplateListState extends State<TemplateListPage> {
.name, .name,
), ),
), ),
),
), ),
), ),
], ],

View File

@ -77,6 +77,7 @@ class TransactionDetailsPage extends StatelessWidget {
child: ListView( child: ListView(
children: [ children: [
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
StreamBuilder( StreamBuilder(
stream: watchBeneficiaryObject( stream: watchBeneficiaryObject(

View File

@ -1,10 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/screen.dart'; import 'package:okane/screen.dart';
import 'package:okane/ui/state/core.dart';
Future<T?> showDialogOrModal<T>({ Future<T?> showDialogOrModal<T>({
required BuildContext context, required BuildContext context,
required WidgetBuilder builder, required WidgetBuilder builder,
bool showDragHandle = true, bool showDragHandle = true,
bool horizontalPaddingOnMobile = true,
}) { }) {
final screenSize = getScreenSize(context); final screenSize = getScreenSize(context);
final width = MediaQuery.sizeOf(context).shortestSide; final width = MediaQuery.sizeOf(context).shortestSide;
@ -18,6 +22,8 @@ Future<T?> showDialogOrModal<T>({
(context) => Padding( (context) => Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: 32 + MediaQuery.of(context).viewInsets.bottom, bottom: 32 + MediaQuery.of(context).viewInsets.bottom,
left: horizontalPaddingOnMobile ? 16 : 0,
right: horizontalPaddingOnMobile ? 16 : 0,
), ),
child: builder(context), child: builder(context),
), ),
@ -26,11 +32,14 @@ Future<T?> showDialogOrModal<T>({
context: context, context: context,
builder: builder:
(context) => Dialog( (context) => Dialog(
child: Container( child: Padding(
constraints: BoxConstraints(maxWidth: width * 0.7), padding: EdgeInsets.only(top: 16),
child: Padding( child: Container(
padding: EdgeInsets.only(bottom: 32), constraints: BoxConstraints(maxWidth: width * 0.7),
child: builder(context), child: Padding(
padding: EdgeInsets.only(bottom: 32),
child: builder(context),
),
), ),
), ),
), ),
@ -38,6 +47,39 @@ Future<T?> showDialogOrModal<T>({
}; };
} }
Future<TransactionTemplate?> selectTransactionTemplate(BuildContext context) {
return showDialogOrModal<TransactionTemplate>(
context: context,
builder: (context) {
return BlocBuilder<CoreCubit, CoreState>(
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]);
},
),
);
},
);
},
);
}
DateTime toMidnight(DateTime t) { DateTime toMidnight(DateTime t) {
return DateTime(t.year, t.month, t.day); return DateTime(t.year, t.month, t.day);
} }

View File

@ -151,44 +151,6 @@ class _AddRecurringTransactionTemplateWidgetState
decoration: InputDecoration(label: Text("Template name")), decoration: InputDecoration(label: Text("Template name")),
), ),
), ),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SearchField<Beneficiary>(
suggestions:
beneficiaries
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id != bloc.activeAccount?.id;
}
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(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
@ -213,6 +175,51 @@ class _AddRecurringTransactionTemplateWidgetState
), ),
), ),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SearchField<Beneficiary>(
suggestions:
beneficiaries
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id != bloc.activeAccount?.id;
}
return true;
})
.map((el) {
return SearchFieldListItem(
getBeneficiaryName(el),
item: el,
);
})
.toList(),
hint: switch (_selectedDirection) {
TransactionDirection.send => "Payee",
TransactionDirection.receive => "Payer",
},
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",
icon: Icon(Icons.euro),
),
),
),
Padding( Padding(
padding: EdgeInsets.only(left: 16, right: 16, top: 16), padding: EdgeInsets.only(left: 16, right: 16, top: 16),
child: SegmentedButton<Period>( child: SegmentedButton<Period>(

View File

@ -39,7 +39,7 @@ class _AddTransactionTemplateWidgetState
TransactionDirection _selectedDirection = TransactionDirection.send; TransactionDirection _selectedDirection = TransactionDirection.send;
ExpenseCategory? _expenseCategory = null; ExpenseCategory? _expenseCategory;
String getBeneficiaryName(Beneficiary item) { String getBeneficiaryName(Beneficiary item) {
return switch (item.type) { return switch (item.type) {
@ -129,68 +129,6 @@ class _AddTransactionTemplateWidgetState
decoration: InputDecoration(label: Text("Template name")), decoration: InputDecoration(label: Text("Template name")),
), ),
), ),
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.account.value?.id !=
bloc.activeAccount?.id;
}
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"),
),
),
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(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
@ -215,6 +153,80 @@ class _AddTransactionTemplateWidgetState
), ),
), ),
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.account.value?.id !=
bloc.activeAccount?.id;
}
return true;
})
.map((el) {
return SearchFieldListItem(
getBeneficiaryName(el),
item: el,
);
})
.toList(),
hint: switch (_selectedDirection) {
TransactionDirection.send => "Payee",
TransactionDirection.receive => "Payer",
},
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",
icon: Icon(Icons.euro),
),
),
),
Row(
children: [
Text("Expense category"),
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 ?? "None"),
),
),
],
),
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: OutlinedButton( child: OutlinedButton(

View File

@ -134,33 +134,7 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
children: [ children: [
OutlinedButton( OutlinedButton(
onPressed: () async { onPressed: () async {
final template = await showDialogOrModal<Transaction>( final template = await selectTransactionTemplate(context);
context: context,
builder: (context) {
return BlocBuilder<CoreCubit, CoreState>(
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) { if (template == null) {
return; return;
} }
@ -180,6 +154,30 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
}, },
child: Text("Use template"), child: Text("Use template"),
), ),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SegmentedButton<TransactionDirection>(
segments: [
ButtonSegment(
value: TransactionDirection.send,
label: Text("Send"),
icon: Icon(Icons.remove),
),
ButtonSegment(
value: TransactionDirection.receive,
label: Text("Receive"),
icon: Icon(Icons.add),
),
],
selected: <TransactionDirection>{_selectedDirection},
multiSelectionEnabled: false,
onSelectionChanged: (selection) {
setState(() => _selectedDirection = selection.first);
},
),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: BlocBuilder<CoreCubit, CoreState>( child: BlocBuilder<CoreCubit, CoreState>(
@ -215,29 +213,6 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
), ),
), ),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SegmentedButton<TransactionDirection>(
segments: [
ButtonSegment(
value: TransactionDirection.send,
label: Text("Send"),
icon: Icon(Icons.remove),
),
ButtonSegment(
value: TransactionDirection.receive,
label: Text("Receive"),
icon: Icon(Icons.add),
),
],
selected: <TransactionDirection>{_selectedDirection},
multiSelectionEnabled: false,
onSelectionChanged: (selection) {
setState(() => _selectedDirection = selection.first);
},
),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField( child: TextField(
@ -246,7 +221,10 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
signed: false, signed: false,
decimal: false, decimal: false,
), ),
decoration: InputDecoration(hintText: "Amount"), decoration: InputDecoration(
hintText: "Amount",
icon: Icon(Icons.euro),
),
), ),
), ),
@ -256,25 +234,28 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text("Date"), Text("Date"),
OutlinedButton( Padding(
onPressed: () async { padding: EdgeInsets.only(left: 16),
final dt = await showDatePicker( child: OutlinedButton(
context: context, onPressed: () async {
initialDate: _selectedDate, final dt = await showDatePicker(
firstDate: DateTime(1), context: context,
lastDate: DateTime(9999), initialDate: _selectedDate,
); firstDate: DateTime(1),
if (dt == null) return; lastDate: DateTime(9999),
);
if (dt == null) return;
setState(() => _selectedDate = dt); setState(() => _selectedDate = dt);
}, },
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Icon(Icons.date_range), Icon(Icons.date_range),
Text(formatDateTime(_selectedDate)), Text(formatDateTime(_selectedDate)),
], ],
),
), ),
), ),
], ],
@ -284,19 +265,22 @@ class _AddTransactionWidgetState extends State<AddTransactionWidget> {
Row( Row(
children: [ children: [
Text("Expense category"), Text("Expense category"),
OutlinedButton( Padding(
onPressed: () async { padding: EdgeInsets.only(left: 16),
final category = await showDialogOrModal( child: OutlinedButton(
context: context, onPressed: () async {
builder: (_) => AddExpenseCategory(), final category = await showDialogOrModal(
); context: context,
if (category == null) { builder: (_) => AddExpenseCategory(),
return; );
} if (category == null) {
return;
}
setState(() => _expenseCategory = category); setState(() => _expenseCategory = category);
}, },
child: Text(_expenseCategory?.name ?? "None"), child: Text(_expenseCategory?.name ?? "None"),
),
), ),
], ],
), ),