diff --git a/lib/domains/charts/chart_bloc.dart b/lib/domains/charts/chart_bloc.dart index 76f77ae..871fd8b 100644 --- a/lib/domains/charts/chart_bloc.dart +++ b/lib/domains/charts/chart_bloc.dart @@ -95,6 +95,8 @@ class ChartBloc extends Bloc { categoriesTotals[transaction.category] = categoryTotal; } + accountsTotals.removeWhere((key, value) => value.round() == 0); + return _computeStateScopedStats(state.copyWith( transactionsLines: transactionsLines, globalTotal: globalTotal, diff --git a/lib/pages/budgets/widgets/budget_comparator.dart b/lib/pages/budgets/widgets/budget_comparator.dart index 75d7251..c622480 100644 --- a/lib/pages/budgets/widgets/budget_comparator.dart +++ b/lib/pages/budgets/widgets/budget_comparator.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:intl/intl.dart'; import 'package:krezus/domains/budget/budget_bloc.dart'; import 'package:krezus/pages/budgets/widgets/budget_cards.dart'; import 'package:krezus/pages/budgets/widgets/budget_compare_selector.dart'; diff --git a/lib/pages/budgets/widgets/budget_maker.dart b/lib/pages/budgets/widgets/budget_maker.dart index 6388fa3..562053a 100644 --- a/lib/pages/budgets/widgets/budget_maker.dart +++ b/lib/pages/budgets/widgets/budget_maker.dart @@ -25,7 +25,7 @@ class BudgetMaker extends StatelessWidget { keyboardType: const TextInputType.numberWithOptions(decimal: false), decoration: InputDecoration( icon: const Icon(Icons.euro), - hintText: '\$\$\$', + label: const Text('Revenu par mois'), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), ), diff --git a/lib/pages/budgets/widgets/budget_projection.dart b/lib/pages/budgets/widgets/budget_projection.dart new file mode 100644 index 0000000..a094108 --- /dev/null +++ b/lib/pages/budgets/widgets/budget_projection.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:krezus/domains/budget/budget_bloc.dart'; +import 'package:krezus/pages/common/titled_container.dart'; + +class BudgetProjection extends StatelessWidget { + const BudgetProjection({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) => TitledContainer( + title: 'Projection', + child: Column( + children: [ + + ], + ) + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/common/editable_color.dart b/lib/pages/common/editable_color.dart new file mode 100644 index 0000000..8a1bb8b --- /dev/null +++ b/lib/pages/common/editable_color.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; + +class EditableColor extends StatelessWidget { + final Color color; + final ValueChanged? onChanged; + + const EditableColor({super.key, required this.color, this.onChanged}); + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () => EditableColorDialog.show(context, color, onChanged), + icon: const Icon(Icons.palette), + color: color, + ); + } +} + +class EditableColorDialog extends StatefulWidget { + final Color initialColor; + final ValueChanged? onChanged; + + const EditableColorDialog({super.key, required this.initialColor, this.onChanged}); + + static void show(BuildContext context, Color color, ValueChanged? onChanged) { + showDialog( + context: context, + barrierDismissible: false, + useRootNavigator: false, + builder: (context) => EditableColorDialog(initialColor: color, onChanged: onChanged) + ); + } + + static void hide(BuildContext context) => Navigator.pop(context); + + @override + State createState() => _EditableColorDialogState(); +} + +class _EditableColorDialogState extends State { + Color? color; + + List _buildColorList() { + return colors.map((color) => IconButton( + onPressed: () => setState(() => this.color = color), + icon: Icon( + color.value == this.color?.value ? Icons.radio_button_checked : Icons.radio_button_off, + color: color, + ) + )).toList(); + } + + @override + Widget build(BuildContext context) { + color ??= widget.initialColor; + return AlertDialog( + actions: [ + IconButton( + onPressed: () => EditableColorDialog.hide(context), + icon: const Icon(Icons.close) + ), + IconButton( + onPressed: () { + EditableColorDialog.hide(context); + if (color != null && widget.onChanged != null) { + widget.onChanged!(color!); + } + }, + icon: const Icon(Icons.save) + ), + ], + title: const Text('Edit color'), + content: Row( + children: _buildColorList(), + ), + ); + } +} + +const List colors = [ + Colors.black, + Colors.white, + Colors.red, + Colors.pink, + Colors.purple, + Colors.indigo, + Colors.blue, + Colors.lightBlue, + Colors.cyan, + Colors.teal, + Colors.green, + Colors.lime, + Colors.yellow, + Colors.amber, + Colors.orange, + Colors.deepOrange, + Colors.brown, + Colors.grey, + Colors.blueGrey, +]; \ No newline at end of file diff --git a/lib/pages/common/editable_label.dart b/lib/pages/common/editable_label.dart new file mode 100644 index 0000000..70bfd5d --- /dev/null +++ b/lib/pages/common/editable_label.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +class EditableLabel extends StatefulWidget { + final String initialValue; + final String? hintText; + final ValueChanged? onChanged; + final TextInputType? keyboardType; + + const EditableLabel({super.key, required this.initialValue, this.onChanged, this.hintText, this.keyboardType}); + + @override + State createState() => _EditableLabelState(); +} + +class _EditableLabelState extends State { + bool editMode = false; + String? value; + + Widget _editMode() { + return Row( + children: [ + SizedBox( + width: 200, + height: 50, + child: TextFormField( + keyboardType: widget.keyboardType, + decoration: InputDecoration( + hintText: widget.hintText, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + initialValue: widget.initialValue, + onChanged: (value) => this.value = value, + ) + ), + IconButton( + onPressed: () => setState(() { + editMode = !editMode; + }), + icon: const Icon(Icons.close), + ), + IconButton( + onPressed: () => setState(() { + editMode = !editMode; + if (value != null && widget.onChanged != null) { + widget.onChanged!(value!); + } + }), + icon: const Icon(Icons.save), + ), + ], + ); + } + + Widget _readMode() { + return Row( + children: [ + Text(widget.initialValue), + IconButton( + onPressed: () => setState(() { + editMode = !editMode; + }), + icon: const Icon(Icons.edit), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return editMode ? _editMode() : _readMode(); + } +} \ No newline at end of file diff --git a/lib/pages/data/widgets/account_settings.dart b/lib/pages/data/widgets/account_settings.dart index 9be586f..ddfa080 100644 --- a/lib/pages/data/widgets/account_settings.dart +++ b/lib/pages/data/widgets/account_settings.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:krezus/domains/account/account_bloc.dart'; +import 'package:krezus/pages/common/editable_color.dart'; +import 'package:krezus/pages/common/editable_label.dart'; import 'package:krezus/pages/common/titled_container.dart'; import 'package:krezus/repositories/metadata/models/account.dart'; @@ -10,10 +12,9 @@ class AccountSettings extends StatelessWidget { List _computeCategoryList(BuildContext context, List accounts) { return accounts.map((account) => Row( children: [ - IconButton( - onPressed: () {}, - icon: const Icon(Icons.palette), + EditableColor( color: account.rgbToColor(), + onChanged: (color) => context.read().add(AccountEditColor(account, color.value.toRadixString(16).toUpperCase().padLeft(8, '0'))), ), IconButton( onPressed: () => context.read().add(AccountEditSaving(account, !account.saving)), @@ -22,11 +23,12 @@ class AccountSettings extends StatelessWidget { ), Container(width: 5), Expanded( - child: Text(account.label) - ), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.edit), + child: EditableLabel( + initialValue: account.label, + onChanged: (value) => context.read().add(AccountEditLabel(account, value)), + hintText: 'Acount name', + keyboardType: TextInputType.text, + ), ), IconButton( onPressed: () => context.read().add(AccountRemove(account)), diff --git a/lib/pages/data/widgets/categories_settings.dart b/lib/pages/data/widgets/categories_settings.dart index 6c8709b..4935594 100644 --- a/lib/pages/data/widgets/categories_settings.dart +++ b/lib/pages/data/widgets/categories_settings.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:krezus/domains/category/category_bloc.dart'; +import 'package:krezus/pages/common/editable_color.dart'; +import 'package:krezus/pages/common/editable_label.dart'; import 'package:krezus/pages/common/titled_container.dart'; import 'package:krezus/repositories/metadata/models/category.dart'; @@ -10,10 +12,9 @@ class CategoriesSettings extends StatelessWidget { List _computeCategoryList(BuildContext context, List categories) { return categories.map((category) => Row( children: [ - IconButton( - onPressed: () {}, - icon: const Icon(Icons.palette), + EditableColor( color: category.rgbToColor(), + onChanged: (color) => context.read().add(CategoryEditColor(category, color.value.toRadixString(16).toUpperCase().padLeft(8, '0'))), ), IconButton( onPressed: () => context.read().add(CategoryEditTransfert(category, !category.transfert)), @@ -27,11 +28,12 @@ class CategoriesSettings extends StatelessWidget { ), Container(width: 5), Expanded( - child: Text(category.label) - ), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.edit), + child: EditableLabel( + initialValue: category.label, + onChanged: (value) => context.read().add(CategoryEditLabel(category, value)), + hintText: 'Acount name', + keyboardType: TextInputType.text, + ), ), IconButton( onPressed: () => context.read().add(CategoryRemove(category)), diff --git a/lib/pages/data/widgets/import_settings.dart b/lib/pages/data/widgets/import_settings.dart index 4850b7d..342375b 100644 --- a/lib/pages/data/widgets/import_settings.dart +++ b/lib/pages/data/widgets/import_settings.dart @@ -16,7 +16,7 @@ class ImportSettings extends StatelessWidget { children: [ FilledButton.icon( onPressed: () => context.read().add(const ClearData()), - label: const Text('ClearData'), + label: const Text('Clear all data'), icon: const Icon(Icons.delete_forever), ), const SizedBox(height: 5), diff --git a/lib/pages/stats/stats_page.dart b/lib/pages/stats/stats_page.dart index 0ffc8b6..1a17a74 100644 --- a/lib/pages/stats/stats_page.dart +++ b/lib/pages/stats/stats_page.dart @@ -80,6 +80,8 @@ class StatsPage extends StatelessWidget { child: SizedBox( height: 450, child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ CategoriesTotalsChart(categoriesTotals: state.scopedCategoriesPositiveTotals, categoriesTotalsPercents: state.scopedSimplifiedCategoriesPositiveTotalsPercents), CategoriesTotalsChart(categoriesTotals: state.scopedCategoriesNegativeTotals, categoriesTotalsPercents: state.scopedSimplifiedCategoriesNegativeTotalsPercents), diff --git a/lib/pages/transactions/widgets/transaction_form.dart b/lib/pages/transactions/widgets/transaction_form.dart index 77a1cd9..2d071f3 100644 --- a/lib/pages/transactions/widgets/transaction_form.dart +++ b/lib/pages/transactions/widgets/transaction_form.dart @@ -15,13 +15,13 @@ class TransactionForm extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ _TransactionDateInput(), - const SizedBox(height: 10,), + const SizedBox(height: 35,), _TransactionCategoryInput(), - const SizedBox(height: 10,), + const SizedBox(height: 35,), _TransactionDescriptionInput(), - const SizedBox(height: 10,), + const SizedBox(height: 35,), _TransactionAccountInput(), - const SizedBox(height: 10,), + const SizedBox(height: 35,), _TransactionValueInput() ], ); @@ -53,7 +53,7 @@ class _TransactionDateInput extends StatelessWidget { }, decoration: InputDecoration( icon: const Icon(Icons.calendar_month), - hintText: 'Date', + label: const Text('Date'), errorText: state.transactionDate.isNotValid ? state.transactionDate.error?.message : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), @@ -79,7 +79,7 @@ class _TransactionCategoryInput extends StatelessWidget { items: categoryState.categories.map((e) => DropdownMenuItem(value: e.label, child: Text(e.label))).toList(), decoration: InputDecoration( icon: const Icon(Icons.category), - hintText: 'Category', + label: const Text('Category'), errorText: state.transactionCategory.isNotValid ? state.transactionCategory.error?.message : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), @@ -101,7 +101,7 @@ class _TransactionDescriptionInput extends StatelessWidget { child: TextFormField( decoration: InputDecoration( icon: const Icon(Icons.description), - hintText: 'Description', + label: const Text('Description'), errorText: state.transactionDescription.isNotValid ? state.transactionDescription.error?.message : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), @@ -129,7 +129,7 @@ class _TransactionAccountInput extends StatelessWidget { items: accountState.accounts.map((e) => DropdownMenuItem(value: e.label, child: Text(e.label))).toList(), decoration: InputDecoration( icon: const Icon(Icons.account_box), - hintText: 'Account', + label: const Text('Account'), errorText: state.transactionAccount.isNotValid ? state.transactionAccount.error?.message : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), @@ -152,7 +152,7 @@ class _TransactionValueInput extends StatelessWidget { keyboardType: TextInputType.number, decoration: InputDecoration( icon: const Icon(Icons.euro), - hintText: '\$\$\$', + label: const Text('\$\$\$'), errorText: state.transactionValue.isNotValid ? state.transactionValue.error?.message : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), diff --git a/lib/repositories/metadata/models/account.dart b/lib/repositories/metadata/models/account.dart index 69d7437..887385c 100644 --- a/lib/repositories/metadata/models/account.dart +++ b/lib/repositories/metadata/models/account.dart @@ -4,11 +4,13 @@ class Account { String label; String color; bool saving; + double interest; Account({ this.label = '', this.color = '', this.saving = false, + this.interest = 0.0, }); factory Account.fromJson(Map json) { @@ -16,6 +18,7 @@ class Account { label: json['label'], color: json['color'], saving: bool.parse(json['saving']), + interest: double.parse(json['intereset']), ); } @@ -23,6 +26,7 @@ class Account { 'label': label, 'color': color, 'saving': saving.toString(), + 'intereset': interest.toString(), }; Color rgbToColor() {