diff --git a/assets/icons/supported_selectable_icons/entertainment/casino.svg b/assets/icons/supported_selectable_icons/entertainment/casino.svg
index 8df94fa3..347465f5 100644
--- a/assets/icons/supported_selectable_icons/entertainment/casino.svg
+++ b/assets/icons/supported_selectable_icons/entertainment/casino.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/entertainment/golf_course.svg b/assets/icons/supported_selectable_icons/entertainment/golf_course.svg
index 7a0641f4..1164ba39 100644
--- a/assets/icons/supported_selectable_icons/entertainment/golf_course.svg
+++ b/assets/icons/supported_selectable_icons/entertainment/golf_course.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/entertainment/roller_skating.svg b/assets/icons/supported_selectable_icons/entertainment/roller_skating.svg
index de8581d4..782e266a 100644
--- a/assets/icons/supported_selectable_icons/entertainment/roller_skating.svg
+++ b/assets/icons/supported_selectable_icons/entertainment/roller_skating.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/food/dinner_dining.svg b/assets/icons/supported_selectable_icons/food/dinner_dining.svg
index 3fd00730..50bd0955 100644
--- a/assets/icons/supported_selectable_icons/food/dinner_dining.svg
+++ b/assets/icons/supported_selectable_icons/food/dinner_dining.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/food/fastfood.svg b/assets/icons/supported_selectable_icons/food/fastfood.svg
index 4f445559..6bf7ab0e 100644
--- a/assets/icons/supported_selectable_icons/food/fastfood.svg
+++ b/assets/icons/supported_selectable_icons/food/fastfood.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/food/kitchen.svg b/assets/icons/supported_selectable_icons/food/kitchen.svg
index 9fb4072a..918c9c01 100644
--- a/assets/icons/supported_selectable_icons/food/kitchen.svg
+++ b/assets/icons/supported_selectable_icons/food/kitchen.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/food/local_pizza.svg b/assets/icons/supported_selectable_icons/food/local_pizza.svg
index a9f72c7b..71439c54 100644
--- a/assets/icons/supported_selectable_icons/food/local_pizza.svg
+++ b/assets/icons/supported_selectable_icons/food/local_pizza.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/food/lunch_dining.svg b/assets/icons/supported_selectable_icons/food/lunch_dining.svg
index 5fd8429d..05b99f56 100644
--- a/assets/icons/supported_selectable_icons/food/lunch_dining.svg
+++ b/assets/icons/supported_selectable_icons/food/lunch_dining.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/food/nutrition.svg b/assets/icons/supported_selectable_icons/food/nutrition.svg
index eac9722e..c4b7c1d7 100644
--- a/assets/icons/supported_selectable_icons/food/nutrition.svg
+++ b/assets/icons/supported_selectable_icons/food/nutrition.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/medical/medication.svg b/assets/icons/supported_selectable_icons/medical/medication.svg
index b890ab81..76a1e2ea 100644
--- a/assets/icons/supported_selectable_icons/medical/medication.svg
+++ b/assets/icons/supported_selectable_icons/medical/medication.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/medical/self_care.svg b/assets/icons/supported_selectable_icons/medical/self_care.svg
index 7141f2b7..31ec9e8c 100644
--- a/assets/icons/supported_selectable_icons/medical/self_care.svg
+++ b/assets/icons/supported_selectable_icons/medical/self_care.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/medical/vaccines.svg b/assets/icons/supported_selectable_icons/medical/vaccines.svg
new file mode 100644
index 00000000..e26b1788
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/medical/vaccines.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/money/payments.svg b/assets/icons/supported_selectable_icons/money/payments.svg
index 081d20ae..aefed13e 100644
--- a/assets/icons/supported_selectable_icons/money/payments.svg
+++ b/assets/icons/supported_selectable_icons/money/payments.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/money/receipt_long.svg b/assets/icons/supported_selectable_icons/money/receipt_long.svg
index 3510e437..dd78ce31 100644
--- a/assets/icons/supported_selectable_icons/money/receipt_long.svg
+++ b/assets/icons/supported_selectable_icons/money/receipt_long.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/money/redeem.svg b/assets/icons/supported_selectable_icons/money/redeem.svg
index 2c9f3dbd..782bcaf8 100644
--- a/assets/icons/supported_selectable_icons/money/redeem.svg
+++ b/assets/icons/supported_selectable_icons/money/redeem.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/app_badging.svg b/assets/icons/supported_selectable_icons/other/app_badging.svg
index 55b5e604..56fbb821 100644
--- a/assets/icons/supported_selectable_icons/other/app_badging.svg
+++ b/assets/icons/supported_selectable_icons/other/app_badging.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/content_cut.svg b/assets/icons/supported_selectable_icons/other/content_cut.svg
new file mode 100644
index 00000000..5b9567ac
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/other/content_cut.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/inventory_2.svg b/assets/icons/supported_selectable_icons/other/inventory_2.svg
index f20adbec..606b84da 100644
--- a/assets/icons/supported_selectable_icons/other/inventory_2.svg
+++ b/assets/icons/supported_selectable_icons/other/inventory_2.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/iron.svg b/assets/icons/supported_selectable_icons/other/iron.svg
index 4a3297e5..3677ba1d 100644
--- a/assets/icons/supported_selectable_icons/other/iron.svg
+++ b/assets/icons/supported_selectable_icons/other/iron.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/pets.svg b/assets/icons/supported_selectable_icons/other/pets.svg
index 1a5cd143..013d8f04 100644
--- a/assets/icons/supported_selectable_icons/other/pets.svg
+++ b/assets/icons/supported_selectable_icons/other/pets.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/recycling.svg b/assets/icons/supported_selectable_icons/other/recycling.svg
new file mode 100644
index 00000000..1c4165cb
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/other/recycling.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/shopping_cart.svg b/assets/icons/supported_selectable_icons/other/shopping_cart.svg
index 8dea2cfb..0f826757 100644
--- a/assets/icons/supported_selectable_icons/other/shopping_cart.svg
+++ b/assets/icons/supported_selectable_icons/other/shopping_cart.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/smoking_rooms.svg b/assets/icons/supported_selectable_icons/other/smoking_rooms.svg
new file mode 100644
index 00000000..6620ba8b
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/other/smoking_rooms.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/sprinkler.svg b/assets/icons/supported_selectable_icons/other/sprinkler.svg
index 7d05d011..7f698c2c 100644
--- a/assets/icons/supported_selectable_icons/other/sprinkler.svg
+++ b/assets/icons/supported_selectable_icons/other/sprinkler.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/other/star.svg b/assets/icons/supported_selectable_icons/other/star.svg
index a7408fe3..c1b8cd49 100644
--- a/assets/icons/supported_selectable_icons/other/star.svg
+++ b/assets/icons/supported_selectable_icons/other/star.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/computer.svg b/assets/icons/supported_selectable_icons/technology/computer.svg
index 379608e5..f35323d4 100644
--- a/assets/icons/supported_selectable_icons/technology/computer.svg
+++ b/assets/icons/supported_selectable_icons/technology/computer.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/devices.svg b/assets/icons/supported_selectable_icons/technology/devices.svg
index 0748d0ba..9df3409c 100644
--- a/assets/icons/supported_selectable_icons/technology/devices.svg
+++ b/assets/icons/supported_selectable_icons/technology/devices.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/monitor.svg b/assets/icons/supported_selectable_icons/technology/monitor.svg
new file mode 100644
index 00000000..5bfc3580
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/technology/monitor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/phone_iphone.svg b/assets/icons/supported_selectable_icons/technology/phone_iphone.svg
new file mode 100644
index 00000000..2e4610f1
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/technology/phone_iphone.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/power.svg b/assets/icons/supported_selectable_icons/technology/power.svg
new file mode 100644
index 00000000..287e8198
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/technology/power.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/print.svg b/assets/icons/supported_selectable_icons/technology/print.svg
new file mode 100644
index 00000000..6fe66f01
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/technology/print.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/sd_card.svg b/assets/icons/supported_selectable_icons/technology/sd_card.svg
new file mode 100644
index 00000000..ad1f4a52
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/technology/sd_card.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/smartphone.svg b/assets/icons/supported_selectable_icons/technology/smartphone.svg
new file mode 100644
index 00000000..b907b069
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/technology/smartphone.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/technology/stadia_controller.svg b/assets/icons/supported_selectable_icons/technology/stadia_controller.svg
index b3b7504b..62e024d7 100644
--- a/assets/icons/supported_selectable_icons/technology/stadia_controller.svg
+++ b/assets/icons/supported_selectable_icons/technology/stadia_controller.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/airplane_ticket.svg b/assets/icons/supported_selectable_icons/transport/airplane_ticket.svg
new file mode 100644
index 00000000..36ea1035
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/transport/airplane_ticket.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/car_rental.svg b/assets/icons/supported_selectable_icons/transport/car_rental.svg
index 5443901c..509b3a73 100644
--- a/assets/icons/supported_selectable_icons/transport/car_rental.svg
+++ b/assets/icons/supported_selectable_icons/transport/car_rental.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/car_repair.svg b/assets/icons/supported_selectable_icons/transport/car_repair.svg
index 59d39a92..d94908f6 100644
--- a/assets/icons/supported_selectable_icons/transport/car_repair.svg
+++ b/assets/icons/supported_selectable_icons/transport/car_repair.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/connecting_airports.svg b/assets/icons/supported_selectable_icons/transport/connecting_airports.svg
new file mode 100644
index 00000000..b45a3c27
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/transport/connecting_airports.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/electric_scooter.svg b/assets/icons/supported_selectable_icons/transport/electric_scooter.svg
index d7e7de80..a14341d4 100644
--- a/assets/icons/supported_selectable_icons/transport/electric_scooter.svg
+++ b/assets/icons/supported_selectable_icons/transport/electric_scooter.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/flight.svg b/assets/icons/supported_selectable_icons/transport/flight.svg
index d6ef403f..ddd1c9bf 100644
--- a/assets/icons/supported_selectable_icons/transport/flight.svg
+++ b/assets/icons/supported_selectable_icons/transport/flight.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/flight_takeoff.svg b/assets/icons/supported_selectable_icons/transport/flight_takeoff.svg
index b6573bf8..82aa016c 100644
--- a/assets/icons/supported_selectable_icons/transport/flight_takeoff.svg
+++ b/assets/icons/supported_selectable_icons/transport/flight_takeoff.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/local_shipping.svg b/assets/icons/supported_selectable_icons/transport/local_shipping.svg
index 9762b033..ddff649d 100644
--- a/assets/icons/supported_selectable_icons/transport/local_shipping.svg
+++ b/assets/icons/supported_selectable_icons/transport/local_shipping.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/local_taxi.svg b/assets/icons/supported_selectable_icons/transport/local_taxi.svg
index e9f04713..b1dea8f6 100644
--- a/assets/icons/supported_selectable_icons/transport/local_taxi.svg
+++ b/assets/icons/supported_selectable_icons/transport/local_taxi.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/luggage.svg b/assets/icons/supported_selectable_icons/transport/luggage.svg
new file mode 100644
index 00000000..6bcac03f
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/transport/luggage.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/supported_selectable_icons/transport/transportation.svg b/assets/icons/supported_selectable_icons/transport/transportation.svg
new file mode 100644
index 00000000..b95922c0
--- /dev/null
+++ b/assets/icons/supported_selectable_icons/transport/transportation.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/app/accounts/account_selector.dart b/lib/app/accounts/account_selector.dart
index 0a076c2a..b405e1b4 100644
--- a/lib/app/accounts/account_selector.dart
+++ b/lib/app/accounts/account_selector.dart
@@ -255,7 +255,7 @@ class _AccountSelectorModalState extends State {
),
if (widget.allowMultiSelection)
ScrollableWithBottomGradient.buildPositionedGradient(
- AppColors.of(context).modalBackground),
+ Theme.of(context).colorSchemeExtended.modalBackground),
]),
);
}
diff --git a/lib/app/accounts/account_type_selector.dart b/lib/app/accounts/account_type_selector.dart
index b1b88694..8fb1470a 100644
--- a/lib/app/accounts/account_type_selector.dart
+++ b/lib/app/accounts/account_type_selector.dart
@@ -77,8 +77,9 @@ class MonekinFilterChip extends StatelessWidget {
borderRadius: BorderRadius.circular(16),
side: BorderSide(
width: 1.25,
- color:
- isSelected ? AppColors.of(context).primary : Colors.transparent,
+ color: isSelected
+ ? Theme.of(context).colorScheme.primary
+ : Colors.transparent,
),
),
clipBehavior: Clip.hardEdge,
@@ -93,7 +94,7 @@ class MonekinFilterChip extends StatelessWidget {
accountType.icon,
size: 28,
color: isSelected
- ? AppColors.of(context).primary
+ ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 18),
@@ -101,7 +102,7 @@ class MonekinFilterChip extends StatelessWidget {
accountType.title(context),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: isSelected
- ? AppColors.of(context).primary
+ ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant),
),
Text(accountType.description(context),
diff --git a/lib/app/accounts/all_accounts_balance.dart b/lib/app/accounts/all_accounts_balance.dart
index 47997c3c..9ccad2a9 100644
--- a/lib/app/accounts/all_accounts_balance.dart
+++ b/lib/app/accounts/all_accounts_balance.dart
@@ -120,7 +120,8 @@ class _AllAccountBalancePageState extends State {
children: [
CardWithHeader(
title: t.stats.balance_by_account,
- bodyPadding: const EdgeInsets.symmetric(vertical: 4),
+ subtitle: t.stats.balance_by_account_subtitle,
+ bodyPadding: const EdgeInsets.only(bottom: 0, top: 8),
body: accounts.isEmpty
? emptyAccountsIndicator()
: ListView.separated(
@@ -129,6 +130,8 @@ class _AllAccountBalancePageState extends State {
final accountWithMoney = accounts[index];
return ListTile(
+ titleAlignment: ListTileTitleAlignment.bottom,
+ minTileHeight: 56,
leading:
accountWithMoney.account.displayIcon(context),
onTap: () => RouteUtils.pushRoute(
@@ -156,7 +159,6 @@ class _AllAccountBalancePageState extends State {
],
),
AnimatedProgressBar(
- width: 6,
value: min(
max(accountWithMoney.money / totalMoney,
0),
@@ -175,6 +177,7 @@ class _AllAccountBalancePageState extends State {
const SizedBox(height: 16),
CardWithHeader(
title: t.stats.balance_by_currency,
+ subtitle: t.stats.balance_by_currency_subtitle,
bodyPadding: const EdgeInsets.symmetric(vertical: 4),
body: Builder(builder: (context) {
final currenciesWithMoney = getCurrenciesWithMoney(accounts);
@@ -189,6 +192,8 @@ class _AllAccountBalancePageState extends State {
final currencyWithMoney = currenciesWithMoney[index];
return ListTile(
+ titleAlignment: ListTileTitleAlignment.bottom,
+ minTileHeight: 56,
leading: StreamBuilder(
stream: CurrencyService.instance.getCurrencyByCode(
currencyWithMoney.currency.code),
@@ -239,7 +244,6 @@ class _AllAccountBalancePageState extends State {
],
),
AnimatedProgressBar(
- width: 6,
value: min(
max(currencyWithMoney.money / totalMoney,
0),
diff --git a/lib/app/accounts/all_accounts_page.dart b/lib/app/accounts/all_accounts_page.dart
index 1e8db490..b58068df 100644
--- a/lib/app/accounts/all_accounts_page.dart
+++ b/lib/app/accounts/all_accounts_page.dart
@@ -101,7 +101,7 @@ class _AllAccountsPageState extends State {
bgColor: AppColors.of(context).light,
margin: const EdgeInsets.symmetric(
horizontal: 0, vertical: 4),
- borderRadius: 12,
+ borderRadius: BorderRadius.circular(12),
child: ListTile(
trailing: accounts.length > 1
? ReorderableDragIcon(
diff --git a/lib/app/accounts/details/account_details.dart b/lib/app/accounts/details/account_details.dart
index c8463c9d..35e2aaa3 100644
--- a/lib/app/accounts/details/account_details.dart
+++ b/lib/app/accounts/details/account_details.dart
@@ -2,6 +2,7 @@ import 'package:drift/drift.dart' as drift;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
+import 'package:monekin/app/accounts/account_form.dart';
import 'package:monekin/app/accounts/details/account_details_actions.dart';
import 'package:monekin/app/transactions/label_value_info_list.dart';
import 'package:monekin/app/transactions/transactions.page.dart';
@@ -11,7 +12,6 @@ import 'package:monekin/core/database/services/exchange-rate/exchange_rate_servi
import 'package:monekin/core/database/services/transaction/transaction_service.dart';
import 'package:monekin/core/models/account/account.dart';
import 'package:monekin/core/models/transaction/transaction_status.enum.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
import 'package:monekin/core/presentation/widgets/bottomSheetFooter.dart';
import 'package:monekin/core/presentation/widgets/card_with_header.dart';
import 'package:monekin/core/presentation/widgets/form_fields/date_form_field.dart';
@@ -106,6 +106,11 @@ class _AccountDetailsPageState extends State {
children: [
CardWithHeader(
title: 'Info',
+ footer: CardFooterWithSingleButton(
+ text: t.general.edit,
+ onButtonClick: () => RouteUtils.pushRoute(
+ context, AccountFormPage(account: account)),
+ ),
body: LabelValueInfoList(items: [
LabelValueInfoListItem(
value: Text(
@@ -148,14 +153,36 @@ class _AccountDetailsPageState extends State {
const SizedBox(height: 16),
CardWithHeader(
title: t.home.last_transactions,
- onHeaderButtonClick: () {
- RouteUtils.pushRoute(
- context,
- TransactionsPage(
- filters: TransactionFilters(
- accountsIDs: [widget.account.id])),
- );
- },
+ bodyPadding:
+ const EdgeInsets.symmetric(vertical: 6),
+ footer: StreamBuilder(
+ stream: TransactionService.instance
+ .countTransactions(
+ predicate: TransactionFilters(
+ status: TransactionStatus.notIn({
+ TransactionStatus.pending,
+ TransactionStatus.voided
+ }),
+ accountsIDs: [widget.account.id],
+ ),
+ ),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData ||
+ snapshot.data!.numberOfRes < 5) {
+ return const SizedBox.shrink();
+ }
+
+ return CardFooterWithSingleButton(
+ onButtonClick: () {
+ RouteUtils.pushRoute(
+ context,
+ TransactionsPage(
+ filters: TransactionFilters(
+ accountsIDs: [widget.account.id],
+ )),
+ );
+ });
+ }),
body: TransactionListComponent(
heroTagBuilder: (tr) =>
'account-details-page__tr-icon-${tr.id}',
@@ -215,7 +242,7 @@ class _AccountDetailHeader extends SliverPersistentHeaderDelegate {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
- color: AppColors.of(context).surface,
+ color: Theme.of(context).colorScheme.surface,
border: Border(
bottom: BorderSide(
width: 2,
diff --git a/lib/app/budgets/budget_details_page.dart b/lib/app/budgets/budget_details_page.dart
index d765623e..26a9f0a1 100644
--- a/lib/app/budgets/budget_details_page.dart
+++ b/lib/app/budgets/budget_details_page.dart
@@ -146,17 +146,18 @@ class _BudgetDetailsPageState extends State {
body: BudgetEvolutionChart(budget: budget)),
const SizedBox(height: 16),
CardWithHeader(
- title: t.stats.by_categories,
- body: ChartByCategories(
- filters: budget.trFilters,
- datePeriodState: budget.periodState,
+ title: t.stats.by_categories,
+ body: ChartByCategories(
+ filters: budget.trFilters,
+ datePeriodState: budget.periodState,
+ ),
+ footer: CardFooterWithSingleButton(
+ onButtonClick: () => RouteUtils.pushRoute(
+ context,
+ const StatsPage(initialIndex: 1),
),
- onHeaderButtonClick: () {
- RouteUtils.pushRoute(
- context,
- const StatsPage(initialIndex: 1),
- );
- }),
+ ),
+ ),
],
),
),
diff --git a/lib/app/budgets/components/budget_evolution_chart.dart b/lib/app/budgets/components/budget_evolution_chart.dart
index dbc8f5d5..02db14d7 100644
--- a/lib/app/budgets/components/budget_evolution_chart.dart
+++ b/lib/app/budgets/components/budget_evolution_chart.dart
@@ -43,7 +43,7 @@ class BudgetEvolutionChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final lineColor = AppColors.of(context).primary;
+ final lineColor = Theme.of(context).colorScheme.primary;
final t = Translations.of(context);
return SizedBox(
@@ -84,7 +84,8 @@ class BudgetEvolutionChart extends StatelessWidget {
]),
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
- getTooltipColor: (spot) => AppColors.of(context).surface,
+ getTooltipColor: (spot) =>
+ Theme.of(context).colorScheme.surface,
tooltipHorizontalAlignment: FLHorizontalAlignment.right,
tooltipMargin: -10,
getTooltipItems: (touchedSpots) {
diff --git a/lib/app/categories/categories_list_page.dart b/lib/app/categories/categories_list_page.dart
index 55ae5a78..46d50a54 100644
--- a/lib/app/categories/categories_list_page.dart
+++ b/lib/app/categories/categories_list_page.dart
@@ -101,7 +101,7 @@ class _CategoriesListPageState extends State {
bgColor: AppColors.of(context).light,
margin: const EdgeInsets.symmetric(
horizontal: 0, vertical: 4),
- borderRadius: 12,
+ borderRadius: BorderRadius.circular(12),
child: ListTile(
trailing: categories.length > 1
? ReorderableDragIcon(
diff --git a/lib/app/categories/form/icon_and_color_selector.dart b/lib/app/categories/form/icon_and_color_selector.dart
index 00782b4f..ec046255 100644
--- a/lib/app/categories/form/icon_and_color_selector.dart
+++ b/lib/app/categories/form/icon_and_color_selector.dart
@@ -31,7 +31,7 @@ class IconAndColorSelector extends StatelessWidget {
return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- color: AppColors.of(context).inputFill,
+ color: Theme.of(context).colorSchemeExtended.inputFill,
borderRadius: BorderRadius.circular(12),
),
child: Row(
@@ -56,7 +56,7 @@ class IconAndColorSelector extends StatelessWidget {
),
);
},
- bgColor: AppColors.of(context).inputFill,
+ bgColor: Theme.of(context).colorSchemeExtended.inputFill,
child: ListTile(
mouseCursor: SystemMouseCursors.click,
title: Text(t.icon_selector.icon),
@@ -64,7 +64,11 @@ class IconAndColorSelector extends StatelessWidget {
const Icon(Icons.arrow_forward_ios_rounded, size: 12),
),
),
- Divider(color: AppColors.of(context).inputFill.darken()),
+ Divider(
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .inputFill
+ .darken()),
Tappable(
onTap: () => showColorPickerModal(
context,
@@ -77,7 +81,7 @@ class IconAndColorSelector extends StatelessWidget {
onDataChange((color: selColor, icon: data.icon));
}),
- bgColor: AppColors.of(context).inputFill,
+ bgColor: Theme.of(context).colorSchemeExtended.inputFill,
child: ListTile(
mouseCursor: SystemMouseCursors.click,
title: Text(t.icon_selector.color),
diff --git a/lib/app/categories/selectors/category_multi_selector.dart b/lib/app/categories/selectors/category_multi_selector.dart
index 8e612687..93b51ccd 100644
--- a/lib/app/categories/selectors/category_multi_selector.dart
+++ b/lib/app/categories/selectors/category_multi_selector.dart
@@ -246,7 +246,7 @@ class _CategoryMultiSelectorModalState
},
),
ScrollableWithBottomGradient.buildPositionedGradient(
- AppColors.of(context).modalBackground),
+ Theme.of(context).colorSchemeExtended.modalBackground),
],
),
);
diff --git a/lib/app/categories/selectors/category_picker.dart b/lib/app/categories/selectors/category_picker.dart
index 799f534e..bca16092 100644
--- a/lib/app/categories/selectors/category_picker.dart
+++ b/lib/app/categories/selectors/category_picker.dart
@@ -144,7 +144,9 @@ class _CategoryPickerState extends State {
// buildSelectAllButton(snapshot),
Expanded(
child: ScrollableWithBottomGradient(
- gradientColor: AppColors.of(context).modalBackground,
+ gradientColor: Theme.of(context)
+ .colorSchemeExtended
+ .modalBackground,
padding: const EdgeInsets.symmetric(vertical: 16),
controller: scrollController,
child: buildCategoryList(snapshot, scrollController),
@@ -241,7 +243,7 @@ class _CategoryPickerState extends State {
style: TextStyle(
color: selectedCategory?.id == subcat.id
? Colors.white
- : AppColors.of(context).onSurface,
+ : Theme.of(context).colorScheme.onSurface,
),
),
showCheckmark: false,
diff --git a/lib/app/currencies/exchange_rate_form.dart b/lib/app/currencies/exchange_rate_form.dart
index 0e261c8a..2c03de9d 100644
--- a/lib/app/currencies/exchange_rate_form.dart
+++ b/lib/app/currencies/exchange_rate_form.dart
@@ -165,7 +165,10 @@ class _ExchangeRateFormDialogState extends State {
child: Icon(
Icons.circle,
size: 25,
- color: AppColors.of(context).inputFill.darken(0.2),
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .inputFill
+ .darken(0.2),
),
),
),
diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart
index 984e23fb..e6174aea 100644
--- a/lib/app/home/dashboard.page.dart
+++ b/lib/app/home/dashboard.page.dart
@@ -4,19 +4,22 @@ import 'package:monekin/app/accounts/account_form.dart';
import 'package:monekin/app/accounts/details/account_details.dart';
import 'package:monekin/app/home/widgets/click_tracker.dart';
import 'package:monekin/app/home/widgets/home_drawer.dart';
+import 'package:monekin/app/home/widgets/horizontal_scrollable_account_list.dart';
import 'package:monekin/app/home/widgets/income_or_expense_card.dart';
import 'package:monekin/app/home/widgets/new_transaction_fl_button.dart';
import 'package:monekin/app/settings/edit_profile_modal.dart';
import 'package:monekin/app/stats/stats_page.dart';
-import 'package:monekin/app/stats/widgets/balance_bar_chart_small.dart';
+import 'package:monekin/app/stats/widgets/balance_bar_chart.dart';
import 'package:monekin/app/stats/widgets/finance_health/finance_health_main_info.dart';
import 'package:monekin/app/stats/widgets/fund_evolution_line_chart.dart';
import 'package:monekin/app/stats/widgets/movements_distribution/chart_by_categories.dart';
import 'package:monekin/core/database/services/account/account_service.dart';
import 'package:monekin/core/database/services/user-setting/private_mode_service.dart';
import 'package:monekin/core/database/services/user-setting/user_setting_service.dart';
+import 'package:monekin/core/extensions/color.extensions.dart';
import 'package:monekin/core/models/account/account.dart';
import 'package:monekin/core/models/date-utils/date_period_state.dart';
+import 'package:monekin/core/presentation/animations/animated_expanded.dart';
import 'package:monekin/core/presentation/responsive/breakpoints.dart';
import 'package:monekin/core/presentation/responsive/responsive_row_column.dart';
import 'package:monekin/core/presentation/widgets/card_with_header.dart';
@@ -44,23 +47,42 @@ class DashboardPage extends StatefulWidget {
class _DashboardPageState extends State {
DatePeriodState dateRangeService = const DatePeriodState();
+ final ScrollController _scrollController = ScrollController();
+ bool showSmallHeader = false;
@override
void initState() {
super.initState();
+
+ _scrollController.addListener(_setSmallHeaderVisible);
}
@override
- Widget build(BuildContext context) {
- final t = Translations.of(context);
+ void dispose() {
+ _scrollController.removeListener(_setSmallHeaderVisible);
+ _scrollController.dispose();
+ super.dispose();
+ }
+ void _setSmallHeaderVisible() {
+ final shouldShowSmallHeader = _scrollController.position.pixels > 150;
+ if (showSmallHeader != shouldShowSmallHeader) {
+ setState(() {
+ showSmallHeader = shouldShowSmallHeader;
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
final accountService = AccountService.instance;
final hideDrawerAndFloatingButton =
BreakPoint.of(context).isLargerOrEqualTo(BreakpointID.md);
return Scaffold(
- appBar: EmptyAppBar(color: AppColors.of(context).light),
+ appBar: EmptyAppBar(
+ color: Theme.of(context).colorSchemeExtended.dashboardHeader),
floatingActionButton:
hideDrawerAndFloatingButton ? null : const NewTransactionButton(),
drawer: hideDrawerAndFloatingButton
@@ -81,263 +103,258 @@ class _DashboardPageState extends State {
);
}),
),
- body: SingleChildScrollView(
- child: Column(children: [
- DefaultTextStyle.merge(
- style:
- TextStyle(color: Theme.of(context).appBarTheme.foregroundColor),
- child: Card(
- margin: const EdgeInsets.only(bottom: 24),
- shape: const RoundedRectangleBorder(
- borderRadius: BorderRadius.only(
- bottomLeft: Radius.circular(16),
- bottomRight: Radius.circular(16),
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.fromLTRB(24, 16, 24, 24),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Tappable(
- onTap: () {
- showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- showDragHandle: true,
- builder: (context) {
- return const EditProfileModal();
- });
- },
- bgColor: Colors.transparent,
- borderRadius: 12,
- child: Padding(
- padding: const EdgeInsets.all(8),
- child: Row(
- children: [
- if (BreakPoint.of(context)
- .isSmallerThan(BreakpointID.md)) ...[
- StreamBuilder(
- stream: UserSettingService.instance
- .getSetting(SettingKey.avatar),
- builder: (context, snapshot) {
- return UserAvatar(
- avatar: snapshot.data);
- }),
- const SizedBox(width: 8),
- ],
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ body: Stack(
+ children: [
+ SingleChildScrollView(
+ controller: _scrollController,
+ child: Column(children: [
+ Card(
+ color: Theme.of(context).colorSchemeExtended.dashboardHeader,
+ margin: const EdgeInsets.only(bottom: 24),
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.only(
+ bottomLeft: Radius.circular(16),
+ bottomRight: Radius.circular(16),
+ ),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(24, 16, 24, 24),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ buildWelcomeMsgAndAvatar(context),
+ buildDatePeriodSelector(context),
+ ],
+ ),
+ Divider(
+ height: 16,
+ color: Theme.of(context)
+ .colorScheme
+ .onPrimaryContainer,
+ ),
+ const SizedBox(height: 8),
+ StreamBuilder(
+ stream: AccountService.instance.getAccounts(),
+ builder: (context, accounts) {
+ return Row(
+ mainAxisAlignment:
+ MainAxisAlignment.spaceBetween,
children: [
- Text(
- "Welcome again!",
- style: Theme.of(context)
- .textTheme
- .bodyMedium!
- .copyWith(
- fontWeight: FontWeight.w300,
- ),
+ totalBalanceIndicator(
+ context, accounts, accountService),
+ Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ IncomeOrExpenseCard(
+ type: TransactionType.E,
+ startDate: dateRangeService.startDate,
+ endDate: dateRangeService.endDate,
+ ),
+ IncomeOrExpenseCard(
+ type: TransactionType.I,
+ startDate: dateRangeService.startDate,
+ endDate: dateRangeService.endDate,
+ ),
+ ],
),
- StreamBuilder(
- stream: UserSettingService.instance
- .getSetting(SettingKey.userName),
- builder: (context, snapshot) {
- if (!snapshot.hasData) {
- return const Skeleton(
- width: 70, height: 12);
- }
-
- return Text(
- snapshot.data!,
- style: Theme.of(context)
- .textTheme
- .titleSmall!
- .copyWith(
- fontWeight: FontWeight.w600,
- fontSize: 18,
- ),
- );
- }),
],
- )
- ],
- ),
- ),
- ),
- ActionChip(
- label: Text(dateRangeService.getText(context)),
- backgroundColor:
- AppColors.of(context).primaryContainer,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.0),
- side: BorderSide(
- style: BorderStyle.none,
- color: AppColors.of(context).onPrimary,
- ),
- ),
- onPressed: () {
- openDatePeriodModal(
- context,
- DatePeriodModal(
- initialDatePeriod: dateRangeService.datePeriod,
- ),
- ).then((value) {
- if (value == null) return;
-
- setState(() {
- dateRangeService = dateRangeService.copyWith(
- periodModifier: 0,
- datePeriod: value,
);
- });
- });
- },
- ),
- ],
- ),
- const Divider(height: 16),
- const SizedBox(height: 8),
- StreamBuilder(
- stream: AccountService.instance.getAccounts(),
- builder: (context, accounts) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- totalBalanceIndicator(
- context, accounts, accountService),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- IncomeOrExpenseCard(
- type: TransactionType.I,
- startDate: dateRangeService.startDate,
- endDate: dateRangeService.endDate,
- ),
- IncomeOrExpenseCard(
- type: TransactionType.E,
- startDate: dateRangeService.startDate,
- endDate: dateRangeService.endDate,
- ),
- ],
- ),
- ],
- );
- },
- ),
- ],
+ })
+ ]),
+ ),
),
- ),
- ),
- ),
- _HorizontalScrollableAccountList(
- dateRangeService: dateRangeService,
- ),
+ HorizontalScrollableAccountList(
+ dateRangeService: dateRangeService,
+ ),
- // ------------- STATS GENERAL CARDS --------------
+ // ------------- STATS GENERAL CARDS --------------
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 24),
- child: ResponsiveRowColumn.withSymetricSpacing(
- direction: BreakPoint.of(context).isLargerThan(BreakpointID.md)
- ? Axis.horizontal
- : Axis.vertical,
- rowCrossAxisAlignment: CrossAxisAlignment.start,
- spacing: 16,
- children: [
- ResponsiveRowColumnItem(
- rowFit: FlexFit.tight,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- CardWithHeader(
- title: t.financial_health.display,
- onHeaderButtonClick: () => RouteUtils.pushRoute(
- context,
- StatsPage(
- dateRangeService: dateRangeService,
- initialIndex: 0)),
- bodyPadding: const EdgeInsets.all(16),
- body: StreamBuilder(
- stream: FinanceHealthService().getHealthyValue(
- filters: TransactionFilters(
- minDate: dateRangeService.startDate,
- maxDate: dateRangeService.endDate,
- ),
- ),
- builder: (context, snapshot) {
- if (!snapshot.hasData) {
- return const LinearProgressIndicator();
- }
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 12,
+ right: 12,
+ top: 20,
+ bottom: 64,
+ ),
+ child: DashboardCards(dateRangeService: dateRangeService),
+ ),
+ ]),
+ ),
+ Positioned(
+ top: 0,
+ left: 0,
+ right: 0,
+ child: AnimatedExpanded(
+ expand: showSmallHeader,
+ child: buildSmallHeader(context),
+ ),
+ )
+ ],
+ ));
+ }
+
+ ActionChip buildDatePeriodSelector(BuildContext context) {
+ return ActionChip(
+ label: Text(dateRangeService.getText(context),
+ style: TextStyle(
+ color: Theme.of(context).colorSchemeExtended.onDashboardHeader)),
+ backgroundColor: Theme.of(context).colorSchemeExtended.dashboardHeader,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ side: BorderSide(
+ // style: BorderStyle.none,
+ color: Theme.of(context).colorSchemeExtended.onDashboardHeader,
+ ),
+ ),
+ onPressed: () {
+ openDatePeriodModal(
+ context,
+ DatePeriodModal(
+ initialDatePeriod: dateRangeService.datePeriod,
+ ),
+ ).then((value) {
+ if (value == null) return;
- final financeHealthData = snapshot.data!;
+ setState(() {
+ dateRangeService = dateRangeService.copyWith(
+ periodModifier: 0,
+ datePeriod: value,
+ );
+ });
+ });
+ },
+ );
+ }
- return FinanceHealthMainInfo(
- financeHealthData: financeHealthData);
- },
- ),
+ Tappable buildWelcomeMsgAndAvatar(BuildContext context) {
+ return Tappable(
+ onTap: () {
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ showDragHandle: true,
+ builder: (context) {
+ return const EditProfileModal();
+ });
+ },
+ bgColor: Colors.transparent,
+ borderRadius: BorderRadius.circular(12),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(4, 8, 24, 8),
+ child: Row(
+ children: [
+ if (BreakPoint.of(context).isSmallerThan(BreakpointID.md)) ...[
+ StreamBuilder(
+ stream:
+ UserSettingService.instance.getSetting(SettingKey.avatar),
+ builder: (context, snapshot) {
+ return UserAvatar(
+ avatar: snapshot.data,
+ backgroundColor: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader
+ .darken(0.25),
+ border: Border.all(
+ width: 2,
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader,
),
- const SizedBox(height: 16),
- CardWithHeader(
- title: t.stats.by_categories,
- body: ChartByCategories(
- datePeriodState: dateRangeService),
- onHeaderButtonClick: () {
- RouteUtils.pushRoute(
- context,
- StatsPage(
- dateRangeService: dateRangeService,
- initialIndex: 1),
- );
- }),
- ],
- ),
+ );
+ }),
+ const SizedBox(width: 12),
+ ],
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Welcome again!",
+ style: Theme.of(context).textTheme.bodyMedium!.copyWith(
+ fontWeight: FontWeight.w300,
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader),
),
- ResponsiveRowColumnItem(
- rowFit: FlexFit.tight,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- CardWithHeader(
- title: t.stats.balance_evolution,
- body: FundEvolutionLineChart(
- dateRange: dateRangeService,
- ),
- onHeaderButtonClick: () {
- RouteUtils.pushRoute(
- context,
- StatsPage(
- dateRangeService: dateRangeService,
- initialIndex: 2),
- );
- }),
- const SizedBox(height: 16),
- CardWithHeader(
- title: t.stats.cash_flow,
- body: Padding(
- padding: const EdgeInsets.only(
- top: 16, left: 16, right: 16),
- child: BalanceChartSmall(
- dateRangeService: dateRangeService),
- ),
- onHeaderButtonClick: () {
- RouteUtils.pushRoute(
- context,
- StatsPage(
- dateRangeService: dateRangeService,
- initialIndex: 3),
- );
- }),
- ],
- ),
- )
+ StreamBuilder(
+ stream: UserSettingService.instance
+ .getSetting(SettingKey.userName),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData) {
+ return const Skeleton(width: 70, height: 12);
+ }
+
+ return Text(
+ snapshot.data!,
+ style: Theme.of(context).textTheme.titleSmall!.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader),
+ );
+ }),
+ const SizedBox(width: 8),
],
- ),
+ )
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget buildSmallHeader(
+ BuildContext context,
+ ) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.only(
+ bottomLeft: Radius.circular(16),
+ bottomRight: Radius.circular(16),
+ ),
+ color: Theme.of(context).colorSchemeExtended.dashboardHeader),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ t.home.total_balance,
+ style: Theme.of(context).textTheme.labelSmall!.copyWith(
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader),
+ ),
+ StreamBuilder(
+ stream: AccountService.instance.getAccountsMoney(),
+ builder: (context, snapshot) {
+ if (snapshot.hasData) {
+ return CurrencyDisplayer(
+ amountToConvert: snapshot.data!,
+ integerStyle: TextStyle(
+ fontSize: 26,
+ fontWeight: FontWeight.w600,
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader),
+ );
+ }
+
+ return const Skeleton(width: 90, height: 40);
+ },
+ ),
+ ],
),
- ])));
+ buildDatePeriodSelector(context)
+ ],
+ ),
+ );
}
Widget totalBalanceIndicator(
@@ -372,7 +389,8 @@ class _DashboardPageState extends State {
children: [
Text(
t.home.total_balance,
- style: Theme.of(context).textTheme.labelSmall!,
+ style: Theme.of(context).textTheme.labelSmall!.copyWith(
+ color: Theme.of(context).colorSchemeExtended.onDashboardHeader),
),
if (!accounts.hasData) ...[
const Skeleton(width: 70, height: 40),
@@ -380,16 +398,17 @@ class _DashboardPageState extends State {
],
if (accounts.hasData) ...[
StreamBuilder(
- stream: accountService.getAccountsMoney(
- accountIds: accounts.data!.map((e) => e.id)),
+ stream: AccountService.instance.getAccountsMoney(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return CurrencyDisplayer(
amountToConvert: snapshot.data!,
- integerStyle: const TextStyle(
- fontSize: 32,
- fontWeight: FontWeight.w600,
- ),
+ integerStyle: TextStyle(
+ fontSize: 32,
+ fontWeight: FontWeight.w600,
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader),
);
}
@@ -412,6 +431,8 @@ class _DashboardPageState extends State {
return TrendingValue(
percentage: snapshot.data!,
fontWeight: FontWeight.bold,
+ filled: true,
+ outlined: true,
fontSize: 16,
);
},
@@ -511,8 +532,9 @@ class _DashboardPageState extends State {
}
}
-class _HorizontalScrollableAccountList extends StatelessWidget {
- const _HorizontalScrollableAccountList({
+class DashboardCards extends StatelessWidget {
+ const DashboardCards({
+ super.key,
required this.dateRangeService,
});
@@ -522,161 +544,103 @@ class _HorizontalScrollableAccountList extends StatelessWidget {
Widget build(BuildContext context) {
final t = Translations.of(context);
- return Align(
- alignment: Alignment.centerLeft,
- child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- physics: const BouncingScrollPhysics(),
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: StreamBuilder(
- stream: AccountService.instance.getAccounts(
- predicate: (acc, curr) => acc.closingDate.isNull(),
- ),
- builder: (context, snapshot) {
- return Row(
- children: [
- ...List.generate(snapshot.data?.length ?? 0, (index) {
- final account = snapshot.data!.elementAt(index);
-
- return Card(
- margin: const EdgeInsets.only(right: 8),
- color: Colors.transparent,
- elevation: 0,
- child: Tappable(
- onTap: () => RouteUtils.pushRoute(
- context,
- AccountDetailsPage(
- account: account,
- accountIconHeroTag:
- 'dashboard-page__account-icon-${account.id}',
- ),
- ),
- bgColor: AppColors.of(context).light,
- borderRadius: 12,
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: SizedBox(
- width: 250,
- child: Column(
- children: [
- Row(children: [
- Hero(
- tag:
- 'dashboard-page__account-icon-${account.id}',
- child: account.displayIcon(
- context,
- size: 28,
- ),
- ),
- const SizedBox(width: 16),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- account.name,
- style: Theme.of(context)
- .textTheme
- .titleMedium!
- .copyWith(
- fontWeight: FontWeight.bold,
- ),
- ),
- Text(
- account.type.title(context),
- style: Theme.of(context)
- .textTheme
- .labelMedium!,
- )
- ],
- )
- ]),
- const Divider(height: 24),
- Row(
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- children: [
- StreamBuilder(
- initialData: 0.0,
- stream: AccountService.instance
- .getAccountMoney(account: account),
- builder: (context, snapshot) {
- return CurrencyDisplayer(
- amountToConvert: snapshot.data!,
- currency: account.currency,
- integerStyle: Theme.of(context)
- .textTheme
- .titleLarge!
- .copyWith(
- fontWeight: FontWeight.w600,
- ),
- );
- }),
- StreamBuilder(
- initialData: 0.0,
- stream: AccountService.instance
- .getAccountsMoneyVariation(
- accounts: [account],
- startDate: dateRangeService.startDate,
- endDate: dateRangeService.endDate,
- convertToPreferredCurrency: false,
- ),
- builder: (context, snapshot) {
- return TrendingValue(
- percentage: snapshot.data!,
- decimalDigits: 0,
- );
- }),
- ],
- ),
- ],
- ),
- ),
- ),
+ return ResponsiveRowColumn.withSymetricSpacing(
+ direction: BreakPoint.of(context).isLargerThan(BreakpointID.md)
+ ? Axis.horizontal
+ : Axis.vertical,
+ rowCrossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 16,
+ children: [
+ ResponsiveRowColumnItem(
+ rowFit: FlexFit.tight,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CardWithHeader(
+ title: t.financial_health.display,
+ footer: CardFooterWithSingleButton(
+ onButtonClick: () => RouteUtils.pushRoute(
+ context,
+ StatsPage(
+ dateRangeService: dateRangeService, initialIndex: 0),
+ ),
+ ),
+ bodyPadding: const EdgeInsets.all(16),
+ body: StreamBuilder(
+ stream: FinanceHealthService().getHealthyValue(
+ filters: TransactionFilters(
+ minDate: dateRangeService.startDate,
+ maxDate: dateRangeService.endDate,
),
+ ),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData) {
+ return const LinearProgressIndicator();
+ }
+
+ final financeHealthData = snapshot.data!;
+
+ return FinanceHealthMainInfo(
+ financeHealthData: financeHealthData);
+ },
+ ),
+ ),
+ const SizedBox(height: 16),
+ CardWithHeader(
+ title: t.stats.by_categories,
+ body: ChartByCategories(datePeriodState: dateRangeService),
+ footer: CardFooterWithSingleButton(
+ onButtonClick: () => RouteUtils.pushRoute(
+ context,
+ StatsPage(
+ dateRangeService: dateRangeService, initialIndex: 1),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ResponsiveRowColumnItem(
+ rowFit: FlexFit.tight,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CardWithHeader(
+ title: t.stats.balance_evolution,
+ bodyPadding: const EdgeInsets.all(16),
+ body: FundEvolutionLineChart(dateRange: dateRangeService),
+ footer: CardFooterWithSingleButton(onButtonClick: () {
+ RouteUtils.pushRoute(
+ context,
+ StatsPage(
+ dateRangeService: dateRangeService, initialIndex: 2),
);
}),
- Opacity(
- opacity: 0.6,
- child: Tappable(
- // bgColor: AppColors.of(context).light,
- onTap: () {
- RouteUtils.pushRoute(context, const AccountFormPage());
- },
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- side: BorderSide(
- width: 2,
- color: Theme.of(context).dividerColor,
- ),
- ),
- child: Card(
- elevation: 0,
- color: Colors.transparent,
- margin: const EdgeInsets.all(0),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: SizedBox(
- width: 200,
- height: 127.3 - 32 - 2,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(t.account.form.create),
- const SizedBox(height: 8),
- const Icon(Icons.add),
- ],
- ),
- ),
- ),
- ),
+ ),
+ const SizedBox(height: 16),
+ CardWithHeader(
+ title: t.stats.by_periods,
+ bodyPadding:
+ const EdgeInsets.only(bottom: 12, top: 24, right: 16),
+ body: BalanceBarChart(
+ dateRange: dateRangeService,
+ filters: TransactionFilters(
+ minDate: dateRangeService.startDate,
+ maxDate: dateRangeService.endDate,
),
- )
- ],
- );
- },
- ),
- ),
+ ),
+ footer: CardFooterWithSingleButton(onButtonClick: () {
+ RouteUtils.pushRoute(
+ context,
+ StatsPage(
+ dateRangeService: dateRangeService, initialIndex: 3),
+ );
+ }),
+ )
+ ],
+ ),
+ )
+ ],
);
}
}
diff --git a/lib/app/home/widgets/home_drawer.dart b/lib/app/home/widgets/home_drawer.dart
index 4eba5f0c..bb68e6e8 100644
--- a/lib/app/home/widgets/home_drawer.dart
+++ b/lib/app/home/widgets/home_drawer.dart
@@ -71,7 +71,7 @@ class HomeDrawer extends StatelessWidget {
return UserAccountsDrawerHeader(
decoration: BoxDecoration(
- color: AppColors.of(context).surface,
+ color: Theme.of(context).colorScheme.surface,
),
accountName: UserGreting(userName: userName),
currentAccountPicture: UserAvatar(avatar: userAvatar),
@@ -107,7 +107,7 @@ class UserGreting extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.w300,
fontSize: 12,
- color: AppColors.of(context).onSurface,
+ color: Theme.of(context).colorScheme.onSurface,
),
),
if (userName == null)
@@ -116,7 +116,7 @@ class UserGreting extends StatelessWidget {
Text(
userName!,
style: TextStyle(
- color: AppColors.of(context).onSurface,
+ color: Theme.of(context).colorScheme.onSurface,
),
),
],
diff --git a/lib/app/home/widgets/horizontal_scrollable_account_list.dart b/lib/app/home/widgets/horizontal_scrollable_account_list.dart
new file mode 100644
index 00000000..25b41d8b
--- /dev/null
+++ b/lib/app/home/widgets/horizontal_scrollable_account_list.dart
@@ -0,0 +1,172 @@
+import 'package:flutter/material.dart';
+import 'package:monekin/app/accounts/account_form.dart';
+import 'package:monekin/app/accounts/details/account_details.dart';
+import 'package:monekin/core/database/services/account/account_service.dart';
+import 'package:monekin/core/models/date-utils/date_period_state.dart';
+import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
+import 'package:monekin/core/presentation/widgets/tappable.dart';
+import 'package:monekin/core/presentation/widgets/trending_value.dart';
+import 'package:monekin/core/routes/route_utils.dart';
+import 'package:monekin/i18n/translations.g.dart';
+
+class HorizontalScrollableAccountList extends StatelessWidget {
+ const HorizontalScrollableAccountList({
+ required this.dateRangeService,
+ });
+
+ final DatePeriodState dateRangeService;
+
+ @override
+ Widget build(BuildContext context) {
+ final t = Translations.of(context);
+
+ return Align(
+ alignment: Alignment.centerLeft,
+ child: SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ physics: const BouncingScrollPhysics(),
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ child: StreamBuilder(
+ stream: AccountService.instance.getAccounts(
+ predicate: (acc, curr) => acc.closingDate.isNull(),
+ ),
+ builder: (context, snapshot) {
+ return Row(
+ children: [
+ ...List.generate(snapshot.data?.length ?? 0, (index) {
+ final account = snapshot.data!.elementAt(index);
+
+ return Card(
+ margin: const EdgeInsets.only(right: 8),
+ color: Colors.transparent,
+ elevation: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(9999),
+ ),
+ child: Tappable(
+ onTap: () => RouteUtils.pushRoute(
+ context,
+ AccountDetailsPage(
+ account: account,
+ accountIconHeroTag:
+ 'dashboard-page__account-icon-${account.id}',
+ ),
+ ),
+ bgColor: Theme.of(context).cardColor,
+ borderRadius: BorderRadius.circular(9999),
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: SizedBox(
+ width: 250,
+ child: Row(
+ children: [
+ Hero(
+ tag:
+ 'dashboard-page__account-icon-${account.id}',
+ child: account.displayIcon(
+ context,
+ size: 28,
+ ),
+ ),
+ const SizedBox(width: 16),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ account.name,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ Row(
+ children: [
+ StreamBuilder(
+ initialData: 0.0,
+ stream: AccountService.instance
+ .getAccountMoney(
+ account: account),
+ builder: (context, snapshot) {
+ return CurrencyDisplayer(
+ amountToConvert: snapshot.data!,
+ currency: account.currency,
+ integerStyle: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontWeight: FontWeight.w600,
+ ),
+ );
+ }),
+ const SizedBox(width: 8),
+ StreamBuilder(
+ initialData: 0.0,
+ stream: AccountService.instance
+ .getAccountsMoneyVariation(
+ accounts: [account],
+ startDate:
+ dateRangeService.startDate,
+ endDate: dateRangeService.endDate,
+ convertToPreferredCurrency: false,
+ ),
+ builder: (context, snapshot) {
+ return TrendingValue(
+ percentage: snapshot.data!,
+ decimalDigits: 0,
+ );
+ }),
+ ],
+ )
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }),
+ // Add account card
+ Opacity(
+ opacity: 0.6,
+ child: Tappable(
+ onTap: () {
+ RouteUtils.pushRoute(context, const AccountFormPage());
+ },
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(99999),
+ side: BorderSide(
+ width: 2,
+ color: Theme.of(context).dividerColor,
+ ),
+ ),
+ child: Card(
+ elevation: 0,
+ color: Colors.transparent,
+ margin: const EdgeInsets.all(0),
+ child: SizedBox(
+ width: 200,
+ height: 80,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(t.account.form.create),
+ const SizedBox(height: 4),
+ const Icon(Icons.add),
+ ],
+ ),
+ ),
+ ),
+ ),
+ )
+ ],
+ );
+ },
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/home/widgets/income_or_expense_card.dart b/lib/app/home/widgets/income_or_expense_card.dart
index 4792c552..c4576b82 100644
--- a/lib/app/home/widgets/income_or_expense_card.dart
+++ b/lib/app/home/widgets/income_or_expense_card.dart
@@ -35,7 +35,7 @@ class IncomeOrExpenseCard extends StatelessWidget {
),
child: Icon(
type.icon,
- color: AppColors.of(context).surface,
+ color: Theme.of(context).colorScheme.surface,
size: 22,
),
),
@@ -43,7 +43,13 @@ class IncomeOrExpenseCard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(type.displayName(context)),
+ Text(
+ type.displayName(context),
+ style: TextStyle(
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader),
+ ),
StreamBuilder(
stream: AccountService.instance.getAccountsBalance(
filters: TransactionFilters(
@@ -61,7 +67,11 @@ class IncomeOrExpenseCard extends StatelessWidget {
return CurrencyDisplayer(
amountToConvert: snapshot.data!.abs(),
- integerStyle: const TextStyle(fontSize: 18),
+ integerStyle: TextStyle(
+ fontSize: 18,
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .onDashboardHeader),
);
})
],
diff --git a/lib/app/layout/tabs.dart b/lib/app/layout/tabs.dart
index cb0d95ec..45cb6fb7 100644
--- a/lib/app/layout/tabs.dart
+++ b/lib/app/layout/tabs.dart
@@ -42,8 +42,12 @@ class TabsPageState extends State {
selectedNavItemIndex < menuItems.length)
? null
: NavigationBar(
+ backgroundColor:
+ Theme.of(context).colorScheme.surfaceContainerHigh,
+ indicatorColor:
+ Theme.of(context).colorScheme.primary.withOpacity(0.2),
destinations: menuItems
- .map((e) => e.toNavigationDestinationWidget())
+ .map((e) => e.toNavigationDestinationWidget(context))
.toList(),
selectedIndex: selectedNavItemIndex,
onDestinationSelected: (e) => changePage(menuItems.elementAt(e)),
diff --git a/lib/app/onboarding/intro.page.dart b/lib/app/onboarding/intro.page.dart
index 44b6c2b4..7752e5d9 100644
--- a/lib/app/onboarding/intro.page.dart
+++ b/lib/app/onboarding/intro.page.dart
@@ -39,7 +39,7 @@ class IntroPage extends StatelessWidget {
style: Theme.of(context)
.textTheme
.labelSmall!
- .copyWith(color: AppColors.of(context).primary),
+ .copyWith(color: Theme.of(context).colorScheme.primary),
),
],
);
diff --git a/lib/app/onboarding/onboarding.dart b/lib/app/onboarding/onboarding.dart
index 66716301..899c4c50 100644
--- a/lib/app/onboarding/onboarding.dart
+++ b/lib/app/onboarding/onboarding.dart
@@ -197,8 +197,8 @@ class _OnboardingPageState extends State {
dotsDecorator: DotsDecorator(
size: const Size.square(10.0),
activeSize: const Size(20.0, 10.0),
- activeColor: AppColors.of(context).primary,
- color: AppColors.of(context).primary.withOpacity(0.3),
+ activeColor: Theme.of(context).colorScheme.primary,
+ color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
spacing: const EdgeInsets.symmetric(horizontal: 3.0),
activeShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.0)),
diff --git a/lib/app/settings/appearance_settings_page.dart b/lib/app/settings/appearance_settings_page.dart
index 0530754a..942c439a 100644
--- a/lib/app/settings/appearance_settings_page.dart
+++ b/lib/app/settings/appearance_settings_page.dart
@@ -218,7 +218,7 @@ class _AdvancedSettingsPageState extends State {
late final Color color;
if (snapshot.data! == 'auto') {
- color = AppColors.of(context).primary;
+ color = Theme.of(context).colorScheme.primary;
} else {
color = ColorHex.get(snapshot.data!);
}
diff --git a/lib/app/settings/import_csv.dart b/lib/app/settings/import_csv.dart
index 91b394c0..cf783e5b 100644
--- a/lib/app/settings/import_csv.dart
+++ b/lib/app/settings/import_csv.dart
@@ -99,7 +99,7 @@ class _ImportCSVPageState extends State {
final t = Translations.of(context);
icon ??= SupportedIconService.instance.defaultSupportedIcon;
- iconColor ??= AppColors.of(context).primary;
+ iconColor ??= Theme.of(context).colorScheme.primary;
return TextFormField(
controller:
diff --git a/lib/app/settings/widgets/setting_card_item.dart b/lib/app/settings/widgets/setting_card_item.dart
index 83c0123c..1dfef4d3 100644
--- a/lib/app/settings/widgets/setting_card_item.dart
+++ b/lib/app/settings/widgets/setting_card_item.dart
@@ -27,15 +27,15 @@ class SettingCardItem extends StatelessWidget {
return Tappable(
bgColor: isPrimary
? isAppInLightBrightness(context)
- ? AppColors.of(context).primary.lighten(0.8)
- : AppColors.of(context).primary.darken(0.8)
+ ? Theme.of(context).colorScheme.primary.lighten(0.8)
+ : Theme.of(context).colorScheme.primary.darken(0.8)
: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
width: 2,
color: isPrimary
- ? AppColors.of(context).primary
+ ? Theme.of(context).colorScheme.primary
: Theme.of(context).dividerColor,
),
),
@@ -51,9 +51,9 @@ class SettingCardItem extends StatelessWidget {
children: [
Icon(
icon,
- color: isPrimary ? AppColors.of(context).primary : null,
+ color: isPrimary ? Theme.of(context).colorScheme.primary : null,
size: mainAxis == Axis.horizontal ? 24 : 28,
- // color: AppColors.of(context).primary,
+ // color: Theme.of(context).colorScheme.primary,
),
if (mainAxis == Axis.horizontal) const SizedBox(width: 12),
if (mainAxis == Axis.vertical) const SizedBox(height: 8),
diff --git a/lib/app/settings/widgets/settings_list_separator.dart b/lib/app/settings/widgets/settings_list_separator.dart
index 34009fee..c23b55db 100644
--- a/lib/app/settings/widgets/settings_list_separator.dart
+++ b/lib/app/settings/widgets/settings_list_separator.dart
@@ -9,7 +9,7 @@ Widget createListSeparator(BuildContext context, String title) {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
- color: AppColors.of(context).primary),
+ color: Theme.of(context).colorScheme.primary),
),
);
}
diff --git a/lib/app/stats/stats_page.dart b/lib/app/stats/stats_page.dart
index 1a48dd8b..2cc0bd05 100644
--- a/lib/app/stats/stats_page.dart
+++ b/lib/app/stats/stats_page.dart
@@ -162,6 +162,9 @@ class _StatsPageState extends State {
buildContainerWithPadding([
CardWithHeader(
title: t.stats.balance_evolution,
+ subtitle: t.stats.balance_evolution_subtitle,
+ bodyPadding: const EdgeInsets.only(
+ bottom: 12, top: 16, right: 16, left: 16),
body: FundEvolutionLineChart(
showBalanceHeader: true,
dateRange: dateRangeService,
@@ -177,6 +180,7 @@ class _StatsPageState extends State {
buildContainerWithPadding([
CardWithHeader(
title: t.stats.cash_flow,
+ subtitle: t.stats.cash_flow_subtitle,
body: IncomeExpenseComparason(
startDate: dateRangeService.startDate,
endDate: dateRangeService.endDate,
@@ -186,7 +190,8 @@ class _StatsPageState extends State {
const SizedBox(height: 16),
CardWithHeader(
title: t.stats.by_periods,
- bodyPadding: const EdgeInsets.only(bottom: 12, top: 16),
+ bodyPadding:
+ const EdgeInsets.only(bottom: 12, top: 24, right: 16),
body: BalanceBarChart(
dateRange: dateRangeService,
filters: filters,
diff --git a/lib/app/stats/widgets/balance_bar_chart.dart b/lib/app/stats/widgets/balance_bar_chart.dart
index aa6662ff..bb199acc 100644
--- a/lib/app/stats/widgets/balance_bar_chart.dart
+++ b/lib/app/stats/widgets/balance_bar_chart.dart
@@ -306,11 +306,16 @@ class _BalanceBarChartState extends State {
: Colors.white24;
return BarChart(BarChartData(
+ maxY: snapshot.data!.expense.every((ex) => ex == 0) &&
+ snapshot.data!.income.every((inc) => inc == 0) &&
+ snapshot.data!.balance.every((bal) => bal == 0)
+ ? 10.2
+ : null,
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
tooltipMargin: -10,
getTooltipColor: (spot) =>
- AppColors.of(context).surface,
+ Theme.of(context).colorScheme.surface,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
final barRodsToY = group.barRods.map((e) => e.toY);
@@ -387,50 +392,42 @@ class _BalanceBarChartState extends State {
},
),
),
- rightTitles: AxisTitles(
+ leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
+ if (value == meta.max) {
+ return Container();
+ }
+
return SideTitleWidget(
axisSide: meta.axisSide,
- space: 0,
- child: Row(
- children: [
- Container(
- width: 5,
- height: 1,
- color: ultraLightBorderColor,
- ),
- const SizedBox(width: 4),
- BlurBasedOnPrivateMode(
- child: Text(
- meta.formattedValue,
- style: const TextStyle(
- fontSize: 10,
- fontWeight: FontWeight.w300,
- ),
- ),
+ child: BlurBasedOnPrivateMode(
+ child: Text(
+ meta.formattedValue,
+ style: const TextStyle(
+ fontSize: 10,
+ fontWeight: FontWeight.w300,
),
- ],
+ ),
),
);
},
reservedSize: 42,
),
),
- leftTitles: const AxisTitles(
+ rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(
- show: true,
- border: Border(
- bottom: BorderSide(
- width: 1, color: ultraLightBorderColor),
- right: BorderSide(
- width: 1, color: ultraLightBorderColor),
- )),
+ show: true,
+ border: Border(
+ bottom:
+ BorderSide(width: 1, color: ultraLightBorderColor),
+ ),
+ ),
gridData: FlGridData(
drawVerticalLine: false,
getDrawingHorizontalLine: (value) {
diff --git a/lib/app/stats/widgets/balance_bar_chart_small.dart b/lib/app/stats/widgets/balance_bar_chart_small.dart
deleted file mode 100644
index 03030730..00000000
--- a/lib/app/stats/widgets/balance_bar_chart_small.dart
+++ /dev/null
@@ -1,232 +0,0 @@
-import 'package:async/async.dart';
-import 'package:fl_chart/fl_chart.dart';
-import 'package:flutter/material.dart';
-import 'package:monekin/core/database/services/account/account_service.dart';
-import 'package:monekin/core/models/date-utils/date_period_state.dart';
-import 'package:monekin/core/presentation/responsive/breakpoints.dart';
-import 'package:monekin/core/presentation/theme.dart';
-import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart';
-import 'package:monekin/i18n/translations.g.dart';
-
-import '../../../core/models/transaction/transaction_type.enum.dart';
-import '../../../core/presentation/app_colors.dart';
-
-class BalanceChartSmall extends StatefulWidget {
- const BalanceChartSmall({super.key, required this.dateRangeService});
-
- final DatePeriodState dateRangeService;
-
- @override
- State createState() => _BalanceChartSmallState();
-}
-
-class _BalanceChartSmallState extends State {
- int touchedGroupIndex = -1;
-
- BarChartGroupData makeGroupData(int x, double expense, double income,
- {bool disabled = false, required AppColors colors}) {
- const double width = 56;
-
- const radius = BorderRadius.vertical(
- bottom: Radius.zero, top: Radius.circular(width / 6));
-
- return BarChartGroupData(
- barsSpace: 4,
- x: x,
- barRods: [
- BarChartRodData(
- toY: expense,
- color: disabled
- ? Colors.grey.withOpacity(0.175)
- : x == 0
- ? colors.danger.withOpacity(0.4)
- : colors.danger,
- borderRadius: radius,
- width: width,
- ),
- BarChartRodData(
- toY: income,
- color: disabled
- ? Colors.grey.withOpacity(0.175)
- : x == 0
- ? colors.success.withOpacity(0.4)
- : colors.success,
- borderRadius: radius,
- width: width,
- ),
- ],
- );
- }
-
- FlTitlesData getTitlesData(Color borderColor) {
- return FlTitlesData(
- show: true,
- rightTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- reservedSize: 32,
- getTitlesWidget: (value, meta) {
- return Row(
- children: [
- Container(
- width: 5,
- height: 1,
- color: borderColor,
- ),
- const SizedBox(width: 4),
- Text(
- meta.formattedValue,
- style: const TextStyle(
- fontSize: 10,
- fontWeight: FontWeight.w300,
- ),
- ),
- ],
- );
- },
- )),
- topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
- bottomTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- reservedSize: 24,
- getTitlesWidget: (value, meta) {
- return Text(
- value == 0 ? 'Periodo anterior' : 'Este periodo',
- style: const TextStyle(
- fontSize: 12,
- fontWeight: FontWeight.w300,
- ),
- );
- },
- ),
- ),
- leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
- );
- }
-
- @override
- Widget build(BuildContext context) {
- final t = Translations.of(context);
-
- return SizedBox(
- height: BreakPoint.of(context).isLargerThan(BreakpointID.md) ? 325 : 250,
- child: StreamBuilder(
- stream: AccountService.instance.getAccounts(),
- builder: (context, accountsSnapshot) {
- if (!accountsSnapshot.hasData) {
- return const CircularProgressIndicator();
- }
-
- final accounts = accountsSnapshot.data!;
-
- final ultraLightBorderColor = isAppInLightBrightness(context)
- ? Colors.black12
- : Colors.white12;
-
- if (accounts.isEmpty) {
- return Stack(
- children: [
- BarChart(
- BarChartData(
- barTouchData: BarTouchData(
- touchTooltipData: BarTouchTooltipData(
- getTooltipItem: (a, b, c, d) => null,
- ),
- ),
- titlesData: getTitlesData(ultraLightBorderColor),
- borderData: FlBorderData(
- show: true,
- border: Border(
- bottom: BorderSide(
- width: 1, color: ultraLightBorderColor),
- ),
- ),
- barGroups: [
- makeGroupData(0, 4, 2,
- disabled: true, colors: AppColors.of(context)),
- makeGroupData(1, 5, 7,
- disabled: true, colors: AppColors.of(context)),
- ],
- gridData: const FlGridData(show: false),
- ),
- ),
- Positioned.fill(
- child: Align(
- alignment: Alignment.center,
- child: Text(
- t.general.insufficient_data,
- style: Theme.of(context).textTheme.titleLarge,
- )),
- )
- ],
- );
- }
-
- return StreamBuilder(
- stream: StreamZip([
- AccountService.instance.getAccountsBalance(
- filters: TransactionFilters(
- transactionTypes: [TransactionType.E],
- minDate: widget.dateRangeService.getPrevDates().$1,
- maxDate: widget.dateRangeService.getPrevDates().$2,
- )),
- AccountService.instance.getAccountsBalance(
- filters: TransactionFilters(
- transactionTypes: [TransactionType.I],
- minDate: widget.dateRangeService.getPrevDates().$1,
- maxDate: widget.dateRangeService.getPrevDates().$2,
- )),
- AccountService.instance.getAccountsBalance(
- filters: TransactionFilters(
- transactionTypes: [TransactionType.E],
- minDate: widget.dateRangeService.startDate,
- maxDate: widget.dateRangeService.endDate,
- )),
- AccountService.instance.getAccountsBalance(
- filters: TransactionFilters(
- transactionTypes: [TransactionType.I],
- minDate: widget.dateRangeService.startDate,
- maxDate: widget.dateRangeService.endDate,
- )),
- ]),
- builder: (context, snapshpot) {
- if (!snapshpot.hasData) {
- return const CircularProgressIndicator();
- }
-
- return BarChart(
- BarChartData(
- barTouchData: BarTouchData(
- touchTooltipData: BarTouchTooltipData(
- getTooltipColor: (group) =>
- AppColors.of(context).surface,
- getTooltipItem: (a, b, c, d) => null,
- ),
- ),
- titlesData: getTitlesData(ultraLightBorderColor),
- borderData: FlBorderData(
- show: true,
- border: Border(
- bottom: BorderSide(
- width: 1, color: ultraLightBorderColor),
- right: BorderSide(
- width: 1, color: ultraLightBorderColor),
- ),
- ),
- barGroups: [
- makeGroupData(
- 0, -snapshpot.data![0], snapshpot.data![1],
- colors: AppColors.of(context)),
- makeGroupData(
- 1, -snapshpot.data![2], snapshpot.data![3],
- colors: AppColors.of(context)),
- ],
- gridData: const FlGridData(show: false),
- ),
- );
- });
- }),
- );
- }
-}
diff --git a/lib/app/stats/widgets/fund_evolution_line_chart.dart b/lib/app/stats/widgets/fund_evolution_line_chart.dart
index 2dc85218..acba34bd 100644
--- a/lib/app/stats/widgets/fund_evolution_line_chart.dart
+++ b/lib/app/stats/widgets/fund_evolution_line_chart.dart
@@ -1,10 +1,10 @@
-import 'package:collection/collection.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:monekin/core/database/services/account/account_service.dart';
import 'package:monekin/core/database/services/currency/currency_service.dart';
import 'package:monekin/core/extensions/color.extensions.dart';
+import 'package:monekin/core/extensions/lists.extensions.dart';
import 'package:monekin/core/models/date-utils/date_period_state.dart';
import 'package:monekin/core/presentation/theme.dart';
import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
@@ -16,8 +16,6 @@ import 'package:monekin/core/utils/constants.dart';
import 'package:monekin/i18n/translations.g.dart';
import 'package:rxdart/rxdart.dart';
-import '../../../core/presentation/app_colors.dart';
-
class LineChartDataItem {
List balance;
List labels;
@@ -70,7 +68,7 @@ class FundEvolutionLineChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final lineColor = AppColors.of(context).primary;
+ final lineColor = Theme.of(context).colorScheme.primary;
final accountService = AccountService.instance;
@@ -78,102 +76,96 @@ class FundEvolutionLineChart extends StatelessWidget {
return Column(
children: [
- if (showBalanceHeader) ...[
+ if (showBalanceHeader)
StreamBuilder(
- stream: filters.accounts(),
- builder: (context, accountsSnapshot) {
- if (!accountsSnapshot.hasData) {
- return Column(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('Final balance - ${dateRange.getText(context)}',
- style: const TextStyle(fontSize: 12)),
- const Skeleton(width: 70, height: 40),
- const Skeleton(width: 30, height: 14),
- ],
- );
- } else {
- final accounts = accountsSnapshot.data!;
+ stream: filters.accounts(),
+ builder: (context, accountsSnapshot) {
+ if (!accountsSnapshot.hasData) {
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('Final balance - ${dateRange.getText(context)}',
+ style: const TextStyle(fontSize: 12)),
+ const Skeleton(width: 70, height: 40),
+ const Skeleton(width: 30, height: 14),
+ ],
+ );
+ } else {
+ final accounts = accountsSnapshot.data!;
- return Padding(
- padding: const EdgeInsets.all(12),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Column(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(t.stats.final_balance,
- style: const TextStyle(fontSize: 12)),
- StreamBuilder(
- stream: accountService.getAccountsMoney(
- accountIds: accounts.map((e) => e.id),
- trFilters: filters,
- date: dateRange.endDate,
- ),
- builder: (context, snapshot) {
- if (!snapshot.hasData) {
- return const Skeleton(
- width: 70, height: 40);
- }
+ Text(t.stats.final_balance,
+ style: const TextStyle(fontSize: 12)),
+ StreamBuilder(
+ stream: accountService.getAccountsMoney(
+ accountIds: accounts.map((e) => e.id),
+ trFilters: filters,
+ date: dateRange.endDate,
+ ),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData) {
+ return const Skeleton(width: 70, height: 40);
+ }
- return CurrencyDisplayer(
- amountToConvert: snapshot.data!,
- integerStyle: Theme.of(context)
- .textTheme
- .headlineSmall!);
- }),
- ],
+ return CurrencyDisplayer(
+ amountToConvert: snapshot.data!,
+ integerStyle: Theme.of(context)
+ .textTheme
+ .headlineSmall!);
+ }),
+ ],
+ ),
+ Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Text(
+ t.stats.compared_to_previous_period,
+ style: const TextStyle(fontSize: 12),
),
- Column(
- mainAxisAlignment: MainAxisAlignment.end,
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- Text(
- t.stats.compared_to_previous_period,
- style: const TextStyle(fontSize: 12),
+ StreamBuilder(
+ stream: accountService.getAccountsMoneyVariation(
+ accounts: accounts,
+ startDate: dateRange.startDate,
+ endDate: dateRange.endDate,
+ convertToPreferredCurrency: true,
),
- StreamBuilder(
- stream:
- accountService.getAccountsMoneyVariation(
- accounts: accounts,
- startDate: dateRange.startDate,
- endDate: dateRange.endDate,
- convertToPreferredCurrency: true,
- ),
- builder: (context, snapshot) {
- if (!snapshot.hasData) {
- return const Skeleton(
- width: 52, height: 22);
- }
+ builder: (context, snapshot) {
+ if (!snapshot.hasData) {
+ return const Skeleton(width: 52, height: 22);
+ }
- return TrendingValue(
- percentage: snapshot.data!,
- filled: false,
- fontWeight: Theme.of(context)
- .textTheme
- .headlineSmall!
- .fontWeight!,
- fontSize: Theme.of(context)
- .textTheme
- .headlineSmall!
- .fontSize!,
- outlined: false,
- );
- })
- ],
- )
+ return TrendingValue(
+ percentage: snapshot.data!,
+ filled: false,
+ fontWeight: Theme.of(context)
+ .textTheme
+ .headlineSmall!
+ .fontWeight!,
+ fontSize: Theme.of(context)
+ .textTheme
+ .headlineSmall!
+ .fontSize!,
+ outlined: false,
+ );
+ })
],
- ),
- );
- }
- }),
- const SizedBox(height: 16),
- ],
+ )
+ ],
+ );
+ }
+ },
+ ),
+ const SizedBox(height: 24),
SizedBox(
- height: 300,
+ height: 260,
child: StreamBuilder(
stream: getEvolutionData(),
builder: (context, snapshot) {
@@ -215,7 +207,7 @@ class FundEvolutionLineChart extends StatelessWidget {
touchTooltipData: LineTouchTooltipData(
tooltipMargin: -10,
getTooltipColor: (spot) =>
- AppColors.of(context).surface,
+ Theme.of(context).colorScheme.surface,
getTooltipItems: (touchedSpots) {
return touchedSpots.map((barSpot) {
final flSpot = barSpot;
@@ -241,25 +233,27 @@ class FundEvolutionLineChart extends StatelessWidget {
),
),
minY: snapshot.hasData
- ? snapshot.data!.balance.min -
- snapshot.data!.balance.min * 0.1
+ ? (snapshot.data!.balance.allItemsEqual()
+ ? snapshot.data!.balance.first - 10.2
+ : null)
: 2,
maxY: snapshot.hasData
- ? snapshot.data!.balance.max +
- snapshot.data!.balance.max * 0.1
+ ? (snapshot.data!.balance.allItemsEqual()
+ ? snapshot.data!.balance.first + 10.2
+ : null)
: 5,
titlesData: FlTitlesData(
show: true,
- leftTitles: const AxisTitles(
+ rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
bottomTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
- rightTitles: AxisTitles(
+ leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: snapshot.hasData,
- reservedSize: 42,
+ reservedSize: 28,
getTitlesWidget: (value, meta) {
if (value == meta.max ||
value == meta.min) {
@@ -268,25 +262,18 @@ class FundEvolutionLineChart extends StatelessWidget {
return SideTitleWidget(
axisSide: meta.axisSide,
- space: 0,
- child: Row(
- children: [
- Container(
- width: 5,
- height: 1,
- color: ultraLightBorderColor,
- ),
- const SizedBox(width: 4),
- BlurBasedOnPrivateMode(
- child: Text(
- meta.formattedValue,
- style: const TextStyle(
- fontSize: 10,
- fontWeight: FontWeight.w300,
- ),
- ),
+ child: BlurBasedOnPrivateMode(
+ child: Text(
+ meta.formattedValue,
+ maxLines: 1,
+ textAlign: TextAlign.end,
+ softWrap: false,
+ overflow: TextOverflow.visible,
+ style: const TextStyle(
+ fontSize: 10,
+ fontWeight: FontWeight.w300,
),
- ],
+ ),
),
);
},
diff --git a/lib/app/stats/widgets/income_expense_comparason.dart b/lib/app/stats/widgets/income_expense_comparason.dart
index 14e1c4a4..d3dd4112 100644
--- a/lib/app/stats/widgets/income_expense_comparason.dart
+++ b/lib/app/stats/widgets/income_expense_comparason.dart
@@ -32,7 +32,7 @@ class IncomeExpenseComparason extends StatelessWidget {
return Column(
children: [
Padding(
- padding: const EdgeInsets.all(12),
+ padding: const EdgeInsets.only(left: 16, right: 16, top: 12),
child: Row(children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
diff --git a/lib/app/stats/widgets/movements_distribution/chart_by_categories.dart b/lib/app/stats/widgets/movements_distribution/chart_by_categories.dart
index 7b0de61b..27448264 100644
--- a/lib/app/stats/widgets/movements_distribution/chart_by_categories.dart
+++ b/lib/app/stats/widgets/movements_distribution/chart_by_categories.dart
@@ -68,16 +68,9 @@ class _ChartByCategoriesState extends State {
}
Future>> getEvolutionData(
- BuildContext context,
- ) async {
+ BuildContext context, List transactions) async {
final data = >[];
- final transactionService = TransactionService.instance;
-
- final transactions = await transactionService
- .getTransactions(filters: _getTransactionFilters())
- .first;
-
for (final transaction in transactions) {
final trValue = transaction.currentValueInPreferredCurrency *
(transactionsType == TransactionType.E ? -1 : 1);
@@ -221,8 +214,10 @@ class _ChartByCategoriesState extends State {
Widget build(BuildContext context) {
final t = Translations.of(context);
- return FutureBuilder(
- future: getEvolutionData(context),
+ return StreamBuilder(
+ stream: TransactionService.instance
+ .getTransactions(filters: _getTransactionFilters())
+ .asyncMap((data) => getEvolutionData(context, data)),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const LinearProgressIndicator();
diff --git a/lib/app/stats/widgets/movements_distribution/tags_stats.dart b/lib/app/stats/widgets/movements_distribution/tags_stats.dart
index 2e1aa1fc..fdeaaa10 100644
--- a/lib/app/stats/widgets/movements_distribution/tags_stats.dart
+++ b/lib/app/stats/widgets/movements_distribution/tags_stats.dart
@@ -41,7 +41,8 @@ class TagStats extends StatelessWidget {
}
if (trSnapshot.data!.isEmpty) {
- return Padding(
+ return Container(
+ alignment: Alignment.center,
padding: const EdgeInsets.all(24),
child: Text(
t.general.insufficient_data,
@@ -67,13 +68,15 @@ class TagStats extends StatelessWidget {
tagsInfo.sort((a, b) => a.value.compareTo(b.value));
if (tags.isEmpty || tagsInfo.isEmpty) {
- return Padding(
+ return Container(
+ alignment: Alignment.center,
padding: const EdgeInsets.all(24),
child: Text(
- tags.isEmpty
- ? t.tags.empty_list
- : t.general.insufficient_data,
- textAlign: TextAlign.center),
+ tags.isEmpty
+ ? t.tags.empty_list
+ : t.general.insufficient_data,
+ textAlign: TextAlign.center,
+ ),
);
}
diff --git a/lib/app/tags/tag_list.page.dart b/lib/app/tags/tag_list.page.dart
index f7475743..010116a0 100644
--- a/lib/app/tags/tag_list.page.dart
+++ b/lib/app/tags/tag_list.page.dart
@@ -78,7 +78,7 @@ class _TagListPageState extends State {
onTap: () => RouteUtils.pushRoute(context, TagFormPage(tag: tag)),
bgColor: AppColors.of(context).light,
margin: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
- borderRadius: 12,
+ borderRadius: BorderRadius.circular(12),
child: ListTile(
trailing: tags.length > 1
? ReorderableDragIcon(
diff --git a/lib/app/tags/tags_selector.modal.dart b/lib/app/tags/tags_selector.modal.dart
index 69325a88..24601e05 100644
--- a/lib/app/tags/tags_selector.modal.dart
+++ b/lib/app/tags/tags_selector.modal.dart
@@ -92,7 +92,7 @@ class _TagSelectorState extends State {
snapSizes: const [0.65],
builder: (context, scrollController) {
return ModalContainer(
- title: t.tags.select,
+ title: t.tags.select.title,
titleBuilder: selectedTags.isEmpty
? null
: (title) {
@@ -177,7 +177,7 @@ class _TagSelectorState extends State {
value: selectedTags.any((element) => element == null),
secondary: Icon(
Icons.label_off_rounded,
- color: AppColors.of(context).primary,
+ color: Theme.of(context).colorScheme.primary,
),
title: Text(t.tags.without_tags),
onChanged: (newValue) {
@@ -222,7 +222,7 @@ class _TagSelectorState extends State {
separatorBuilder: (context, index) => const Divider(),
),
ScrollableWithBottomGradient.buildPositionedGradient(
- AppColors.of(context).modalBackground),
+ Theme.of(context).colorSchemeExtended.modalBackground),
],
),
);
diff --git a/lib/app/transactions/form/dialogs/amount_selector.dart b/lib/app/transactions/form/dialogs/amount_selector.dart
new file mode 100644
index 00000000..2ea6297a
--- /dev/null
+++ b/lib/app/transactions/form/dialogs/amount_selector.dart
@@ -0,0 +1,587 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:monekin/app/transactions/form/dialogs/evaluate_expression.dart';
+import 'package:monekin/core/database/app_db.dart';
+import 'package:monekin/core/extensions/bool.extension.dart';
+import 'package:monekin/core/extensions/numbers.extensions.dart';
+import 'package:monekin/core/presentation/animations/animated_expanded.dart';
+import 'package:monekin/core/presentation/app_colors.dart';
+import 'package:monekin/core/presentation/widgets/modal_container.dart';
+import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
+
+class AmountSelector extends StatefulWidget {
+ const AmountSelector({
+ super.key,
+ required this.initialAmount,
+ this.currency,
+ this.onSubmit,
+ this.enableSignToggleButton = true,
+ required this.title,
+ });
+
+ final String title;
+
+ final double initialAmount;
+ final CurrencyInDB? currency;
+
+ /// Display a button to change the sign of the current value (when the calculator is not enabled)
+ final bool enableSignToggleButton;
+
+ final void Function(double amount)? onSubmit;
+
+ @override
+ State createState() => _AmountSelectorState();
+}
+
+class _AmountSelectorState extends State {
+ late String amountString;
+
+ double get valueToNumber {
+ if (amountString.trim() == '') {
+ return 0;
+ } else if (amountString.trim() == '-' || amountString.trim() == '-0') {
+ return -0;
+ }
+
+ return evaluateExpression(amountString).roundWithDecimals(2);
+ }
+
+ final FocusNode _focusNode = FocusNode();
+ late FocusAttachment _focusAttachment;
+
+ bool calculatorMode = false;
+
+ @override
+ void initState() {
+ super.initState();
+
+ amountString = _parseInitialAmount(widget.initialAmount);
+
+ _focusAttachment = _focusNode.attach(context, onKeyEvent: (node, event) {
+ bool keyIsPressed = event.runtimeType == KeyDownEvent ||
+ event.runtimeType == KeyRepeatEvent;
+
+ if (!keyIsPressed) {
+ return KeyEventResult.handled;
+ }
+
+ if ((event.logicalKey == LogicalKeyboardKey.browserBack ||
+ event.logicalKey == LogicalKeyboardKey.goBack ||
+ event.logicalKey == LogicalKeyboardKey.escape)) {
+ Navigator.pop(context);
+ }
+
+ for (final (index, element) in [
+ LogicalKeyboardKey.digit0,
+ LogicalKeyboardKey.digit1,
+ LogicalKeyboardKey.digit2,
+ LogicalKeyboardKey.digit3,
+ LogicalKeyboardKey.digit4,
+ LogicalKeyboardKey.digit5,
+ LogicalKeyboardKey.digit6,
+ LogicalKeyboardKey.digit7,
+ LogicalKeyboardKey.digit8,
+ LogicalKeyboardKey.digit9,
+ ].indexed) {
+ if (event.logicalKey == element) {
+ addToAmount(index.toStringAsFixed(0));
+ break;
+ }
+ }
+
+ for (final (index, element) in [
+ LogicalKeyboardKey.numpad0,
+ LogicalKeyboardKey.numpad1,
+ LogicalKeyboardKey.numpad2,
+ LogicalKeyboardKey.numpad3,
+ LogicalKeyboardKey.numpad4,
+ LogicalKeyboardKey.numpad5,
+ LogicalKeyboardKey.numpad6,
+ LogicalKeyboardKey.numpad7,
+ LogicalKeyboardKey.numpad8,
+ LogicalKeyboardKey.numpad9,
+ ].indexed) {
+ if (event.logicalKey == element) {
+ addToAmount(index.toStringAsFixed(0));
+ break;
+ }
+ }
+
+ if (event.logicalKey == LogicalKeyboardKey.period) {
+ addToAmount('.');
+ } else if (event.logicalKey == LogicalKeyboardKey.numpadDecimal) {
+ addToAmount('.');
+ } else if (event.logicalKey == LogicalKeyboardKey.comma) {
+ addToAmount('.');
+ } else if (event.logicalKey == LogicalKeyboardKey.backspace) {
+ removeLastCharFromAmount();
+ } else if (event.logicalKey == LogicalKeyboardKey.delete) {
+ removeLastCharFromAmount();
+ } else if (event.logicalKey == LogicalKeyboardKey.enter) {
+ submitAmount();
+ }
+
+ return KeyEventResult.handled;
+ });
+
+ _focusNode.requestFocus();
+ }
+
+ @override
+ void dispose() {
+ _focusNode.dispose();
+ super.dispose();
+ }
+
+ String _parseInitialAmount(double amount) {
+ if (amount == 0) {
+ return amount.isNegative ? '-' : '';
+ }
+
+ String stringAmount = amount.toStringAsFixed(2);
+ if (!stringAmount.contains('.')) {
+ return stringAmount;
+ }
+
+ int index = stringAmount.length - 1;
+ while (stringAmount[index] == '0') {
+ index--;
+ }
+ if (stringAmount[index] == '.') {
+ index--;
+ }
+
+ return stringAmount.substring(0, index + 1);
+ }
+
+ bool _currentNumberHasDecimal() {
+ final exprSplit = splitExprByNumbersAndOperator(amountString);
+
+ if (exprSplit.isEmpty) {
+ return false;
+ }
+
+ return exprSplit.last.contains('.');
+ }
+
+ toggleSign() {
+ if (amountString.startsWith('-')) {
+ amountString = amountString.substring(1, amountString.length);
+ } else {
+ amountString = '-$amountString';
+ }
+
+ HapticFeedback.mediumImpact();
+
+ setState(() {});
+ }
+
+ void addToAmount(String newText) {
+ if (newText == '.' && _currentNumberHasDecimal()) {
+ return;
+ }
+
+ final newInputIsOperator = CalculatorOperator.isOperator(newText);
+
+ setNewAmount(String newSelectedAmount) {
+ if (newInputIsOperator) {
+ HapticFeedback.mediumImpact();
+ } else {
+ HapticFeedback.selectionClick();
+ }
+
+ setState(() {
+ amountString = newSelectedAmount;
+ });
+ }
+
+ if (newInputIsOperator && !calculatorMode) {
+ return;
+ } else if (valueToNumber != 0 &&
+ double.tryParse(newText) != null &&
+ _currentNumberHasDecimal() &&
+ !CalculatorOperator.exprHasOperator(amountString)) {
+ final decimalPlaces = splitExprByNumbersAndOperator(amountString)
+ .last
+ .split('.')
+ .elementAtOrNull(1);
+
+ if (decimalPlaces != null && decimalPlaces.length >= 2) {
+ return;
+ }
+
+ // Pass
+ }
+
+ if (amountString.isEmpty ||
+ amountString == CalculatorOperator.subtract.symbol) {
+ if (newText == '0') {
+ return;
+ } else if (newText == '.') {
+ if (valueToNumber.isNegative) {
+ setNewAmount('-0.');
+ } else {
+ setNewAmount('0.');
+ }
+ } else if (newInputIsOperator) {
+ setNewAmount('0$newText');
+ } else {
+ final sign = valueToNumber.isNegative ? '-' : '';
+
+ setNewAmount('$sign$newText');
+ }
+ } else if (CalculatorOperator.exprEndsWithOperator(amountString)) {
+ if (newText == '.') {
+ setNewAmount('${amountString}0.');
+ } else if (newInputIsOperator) {
+ // Replace last operator:
+ setNewAmount(
+ amountString.substring(0, amountString.length - 1) + newText);
+ } else {
+ setNewAmount(amountString + newText);
+ }
+ } else {
+ setNewAmount(amountString + newText);
+ }
+ }
+
+ submitAmount() {
+ HapticFeedback.lightImpact();
+
+ if (widget.onSubmit != null) {
+ widget.onSubmit!(valueToNumber);
+ }
+ }
+
+ void clearAmount() {
+ setState(() {
+ amountString = '0';
+ HapticFeedback.lightImpact();
+ });
+ }
+
+ void removeLastCharFromAmount() {
+ if (amountString.isEmpty ||
+ amountString == CalculatorOperator.subtract.symbol) {
+ return;
+ }
+
+ setState(() {
+ amountString = amountString.substring(0, amountString.length - 1);
+ HapticFeedback.lightImpact();
+ });
+ }
+
+ toggleCalculatorMode() {
+ calculatorMode = !calculatorMode;
+
+ if (calculatorMode == false) {
+ amountString = _parseInitialAmount(valueToNumber);
+ }
+
+ HapticFeedback.mediumImpact();
+
+ setState(() {});
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ _focusAttachment.reparent();
+
+ return ModalContainer(
+ title: widget.title,
+ bodyPadding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
+ body: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Padding(
+ padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ AnimatedExpanded(
+ expand: CalculatorOperator.exprHasOperator(amountString),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Text(amountString),
+ ],
+ )),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Builder(builder: (context) {
+ const bigSizeStyle = TextStyle(
+ fontSize: 32,
+ fontWeight: FontWeight.w700,
+ );
+
+ return CurrencyDisplayer(
+ amountToConvert: valueToNumber,
+ currency: widget.currency,
+ decimalsStyle: TextStyle(
+ fontWeight: FontWeight.w200,
+ fontSize: 22,
+ color: amountString.contains('.')
+ ? null
+ : Theme.of(context)
+ .colorScheme
+ .onSurface
+ .withOpacity(0.3)),
+ integerStyle: bigSizeStyle,
+ currencyStyle: bigSizeStyle,
+ );
+ })
+ ],
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 12),
+ const Divider(),
+ Flexible(
+ child: Container(
+ // height: min(MediaQuery.of(context).size.width * 0.8, 300),
+ margin: const EdgeInsets.only(top: 16),
+ decoration: const BoxDecoration(
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(16),
+ topRight: Radius.circular(16),
+ ),
+ ),
+ child: Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CalculatorButton(
+ onClick: () => addToAmount(
+ CalculatorOperator.multiply.symbol),
+ text: '×',
+ style: CalculatorButtonStyle.secondary,
+ flex: calculatorMode.toInt(),
+ ),
+ CalculatorButton(
+ onClick: () => addToAmount('1'), text: '1'),
+ CalculatorButton(
+ onClick: () => addToAmount('4'), text: '4'),
+ CalculatorButton(
+ onClick: () => addToAmount('7'), text: '7'),
+ CalculatorButton(
+ onClick: () => addToAmount('0'), text: '0'),
+ ],
+ ),
+ ),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CalculatorButton(
+ onClick: () =>
+ addToAmount(CalculatorOperator.divide.symbol),
+ text: '÷',
+ style: CalculatorButtonStyle.secondary,
+ flex: calculatorMode.toInt(),
+ ),
+ CalculatorButton(
+ onClick: () => addToAmount('2'), text: '2'),
+ CalculatorButton(
+ onClick: () => addToAmount('5'), text: '5'),
+ CalculatorButton(
+ onClick: () => addToAmount('8'), text: '8'),
+ CalculatorButton(
+ disabled: _currentNumberHasDecimal(),
+ onClick: () => addToAmount('.'),
+ text: '.',
+ ),
+ ],
+ ),
+ ),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CalculatorButton(
+ onClick: () => addToAmount(
+ CalculatorOperator.subtract.symbol),
+ text: '-',
+ style: CalculatorButtonStyle.secondary,
+ flex: calculatorMode.toInt(),
+ ),
+ CalculatorButton(
+ onClick: () => addToAmount('3'), text: '3'),
+ CalculatorButton(
+ onClick: () => addToAmount('6'), text: '6'),
+ CalculatorButton(
+ onClick: () => addToAmount('9'), text: '9'),
+ CalculatorButton(
+ onClick: toggleCalculatorMode,
+ style: CalculatorButtonStyle.secondary,
+ icon: calculatorMode
+ ? Icons.fullscreen_exit_rounded
+ : Icons.calculate_rounded,
+ ),
+ ],
+ ),
+ ),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CalculatorButton(
+ onClick: () =>
+ addToAmount(CalculatorOperator.add.symbol),
+ text: '+',
+ style: CalculatorButtonStyle.secondary,
+ flex: calculatorMode.toInt(),
+ ),
+ CalculatorButton(
+ onClick: removeLastCharFromAmount,
+ onLongPress: clearAmount,
+ style: CalculatorButtonStyle.secondary,
+ icon: Icons.backspace_outlined,
+ ),
+ CalculatorButton(
+ onClick: toggleSign,
+ style: CalculatorButtonStyle.secondary,
+ icon: Icons.exposure_rounded,
+ flex: calculatorMode ||
+ !widget.enableSignToggleButton
+ ? 0
+ : 1,
+ ),
+ CalculatorButton(
+ disabled: valueToNumber == 0 ||
+ valueToNumber.isInfinite ||
+ valueToNumber.isNaN,
+ onClick: submitAmount,
+ icon: Icons.check_rounded,
+ style: CalculatorButtonStyle.submit,
+ flex: calculatorMode ||
+ !widget.enableSignToggleButton
+ ? 3
+ : 2,
+ ),
+ ],
+ ),
+ ),
+ ],
+ )),
+ )
+ ]),
+ );
+ }
+}
+
+enum CalculatorButtonStyle { submit, main, secondary }
+
+class CalculatorButton extends StatelessWidget {
+ final String? text;
+ final IconData? icon;
+ final int flex;
+ final VoidCallback? onClick;
+ final VoidCallback? onLongPress;
+
+ final bool disabled;
+
+ final CalculatorButtonStyle style;
+
+ const CalculatorButton({
+ super.key,
+ this.text,
+ this.icon,
+ required this.onClick,
+ this.onLongPress,
+ this.flex = 1,
+ this.disabled = false,
+ this.style = CalculatorButtonStyle.main,
+ }) : assert((text != null && icon == null) || (text == null && icon != null),
+ 'You must specify either text or icon, not both.');
+
+ @override
+ Widget build(BuildContext context) {
+ EdgeInsets padding =
+ const EdgeInsets.symmetric(vertical: 2.5, horizontal: 2.5);
+ if (MediaQuery.of(context).size.width >= 600) {
+ padding = const EdgeInsets.symmetric(vertical: 4, horizontal: 5);
+ } else if (MediaQuery.of(context).size.width >= 1024) {
+ padding = const EdgeInsets.symmetric(vertical: 4, horizontal: 32);
+ }
+
+ return AnimatedSize(
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.fastEaseInToSlowEaseOut,
+ child: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 250),
+ switchInCurve: Curves.fastEaseInToSlowEaseOut,
+ switchOutCurve: Curves.fastOutSlowIn,
+ transitionBuilder: (Widget child, Animation animation) {
+ return ScaleTransition(scale: animation, child: child);
+ },
+ child: Container(
+ key: ValueKey((text ?? icon.toString()) + flex.toString()),
+ height: 65.0 * flex,
+ width: double.infinity,
+ clipBehavior: Clip.hardEdge,
+ decoration: const BoxDecoration(),
+ padding: padding,
+ child: elevatedButton(context),
+ ),
+ ),
+ );
+ }
+
+ ElevatedButton elevatedButton(BuildContext context) {
+ Color effectiveTextColor = Theme.of(context).colorScheme.onSurface;
+ Color effectiveBgColor = Theme.of(context).colorScheme.surface;
+
+ if (style == CalculatorButtonStyle.submit) {
+ effectiveTextColor = Theme.of(context).colorScheme.onPrimary;
+ effectiveBgColor = Theme.of(context).colorScheme.primary;
+ } else if (style == CalculatorButtonStyle.secondary) {
+ effectiveTextColor =
+ Theme.of(context).colorScheme.onSurface.withOpacity(0.9);
+ effectiveBgColor = Theme.of(context).colorScheme.surfaceContainerHigh;
+ }
+
+ if (icon == Icons.backspace_outlined) {
+ effectiveTextColor = AppColors.of(context).danger;
+ }
+
+ return ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Theme.of(context).brightness == Brightness.light
+ ? effectiveBgColor.withOpacity(0.975)
+ : effectiveBgColor.withOpacity(0.85),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ shadowColor: effectiveBgColor.withOpacity(0.85),
+ surfaceTintColor: effectiveBgColor.withOpacity(0.85),
+ foregroundColor: effectiveTextColor,
+ disabledForegroundColor: effectiveTextColor.withOpacity(0.3),
+ disabledBackgroundColor: effectiveBgColor.withOpacity(0.3),
+ elevation: 0,
+ padding: const EdgeInsets.all(0),
+ ),
+ onPressed: disabled ? null : onClick,
+ onLongPress: disabled ? null : onLongPress,
+ child: icon != null
+ ? Icon(icon, size: 26)
+ : Text(
+ text!,
+ softWrap: false,
+ style: const TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/transactions/form/dialogs/evaluate_expression.dart b/lib/app/transactions/form/dialogs/evaluate_expression.dart
new file mode 100644
index 00000000..dfe53783
--- /dev/null
+++ b/lib/app/transactions/form/dialogs/evaluate_expression.dart
@@ -0,0 +1,169 @@
+// ignore_for_file: prefer_single_quotes
+
+enum CalculatorOperator {
+ add('+'),
+ subtract('-'),
+ multiply('X'),
+ divide('÷');
+
+ final String symbol;
+
+ const CalculatorOperator(this.symbol);
+
+ @override
+ String toString() {
+ return symbol;
+ }
+
+ static CalculatorOperator? fromString(String symbol) {
+ switch (symbol) {
+ case '+':
+ return CalculatorOperator.add;
+ case '-':
+ return CalculatorOperator.subtract;
+ case '*' || 'x' || 'X':
+ return CalculatorOperator.multiply;
+ case '/' || '÷':
+ return CalculatorOperator.divide;
+ default:
+ return null;
+ }
+ }
+
+ double apply(double a, double b) {
+ switch (this) {
+ case CalculatorOperator.add:
+ return a + b;
+ case CalculatorOperator.subtract:
+ return a - b;
+ case CalculatorOperator.multiply:
+ return a * b;
+ case CalculatorOperator.divide:
+ return a / b;
+ }
+ }
+
+ static bool exprHasOperator(String expression) {
+ expression = expression.trim();
+
+ for (int i = 1; i < expression.length; i++) {
+ // Start by zero to avoid returning true when the first number is negative
+ final char = expression[i];
+ if (CalculatorOperator.fromString(char) != null) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ static bool isOperator(String char) {
+ if (char.length > 1) {
+ throw ArgumentError("Character can not have this legth");
+ }
+
+ return CalculatorOperator.fromString(char) != null;
+ }
+
+ static bool exprEndsWithOperator(String expression) {
+ if (expression.isEmpty) {
+ return false;
+ }
+
+ final lastChar = expression[expression.length - 1];
+ return CalculatorOperator.fromString(lastChar) != null;
+ }
+
+ static Iterable getAllSymbols() {
+ return values.map((op) => op.symbol);
+ }
+}
+
+List splitExprByNumbersAndOperator(String expression) {
+ final operators = CalculatorOperator.getAllSymbols().join("|\\");
+ // ignore: prefer_interpolation_to_compose_strings
+ return RegExp(r'(\d+\.?\d*|\' + operators + ')')
+ .allMatches(expression)
+ .map((m) => m.group(0)!)
+ .toList();
+}
+
+double evaluateExpression(String expression) {
+ // Remove any whitespace from the input string
+ expression = expression.replaceAll(' ', '');
+
+ // Handle negative sign at the start of the expression
+ if (expression.startsWith('-')) {
+ expression =
+ '0$expression'; // Prepend 0 to allow for correct parsing, e.g., "-3+4" becomes "0-3+4"
+ }
+
+ // Ignore trailing operators by removing them if present
+ while (expression.isNotEmpty &&
+ CalculatorOperator.fromString(expression[expression.length - 1]) !=
+ null) {
+ expression = expression.substring(0, expression.length - 1);
+ }
+
+ if (expression.isEmpty) {
+ throw ArgumentError('Invalid expression: no numbers found.');
+ }
+
+ final tokens = splitExprByNumbersAndOperator(expression);
+
+ List postfix = _infixToPostfix(tokens);
+ return _evaluatePostfix(postfix);
+}
+
+List _infixToPostfix(List tokens) {
+ final precedence = {
+ CalculatorOperator.add: 1,
+ CalculatorOperator.subtract: 1,
+ CalculatorOperator.multiply: 2,
+ CalculatorOperator.divide: 2,
+ };
+ final operators = [];
+ final output = [];
+
+ for (int i = 0; i < tokens.length; i++) {
+ final token = tokens[i];
+ final op = CalculatorOperator.fromString(token);
+
+ if (double.tryParse(token) != null) {
+ // If the token is a number, add it to the output
+ output.add(token);
+ } else if (op != null) {
+ // While the top of the operator stack has the same or greater precedence
+ while (operators.isNotEmpty &&
+ precedence[operators.last]! >= precedence[op]!) {
+ output.add(operators.removeLast().toString());
+ }
+ // Push the current operator to the stack
+ operators.add(op);
+ }
+ }
+
+ // Pop any remaining operators onto the output
+ while (operators.isNotEmpty) {
+ output.add(operators.removeLast().toString());
+ }
+
+ return output;
+}
+
+double _evaluatePostfix(List postfix) {
+ final stack = [];
+
+ for (final token in postfix) {
+ if (double.tryParse(token) != null) {
+ stack.add(double.parse(token));
+ } else {
+ final b = stack.removeLast();
+ final a = stack.removeLast();
+ final op = CalculatorOperator.fromString(token)!;
+ stack.add(op.apply(a, b));
+ }
+ }
+
+ return stack.last;
+}
diff --git a/lib/app/transactions/form/dialogs/transaction_more_info.modal.dart b/lib/app/transactions/form/dialogs/transaction_more_info.modal.dart
deleted file mode 100644
index aefdee72..00000000
--- a/lib/app/transactions/form/dialogs/transaction_more_info.modal.dart
+++ /dev/null
@@ -1,190 +0,0 @@
-import 'package:copy_with_extension/copy_with_extension.dart';
-import 'package:flutter/material.dart';
-import 'package:monekin/app/tags/tags_selector.modal.dart';
-import 'package:monekin/app/transactions/form/dialogs/transaction_value_in_destiny_modal.dart';
-import 'package:monekin/core/extensions/lists.extensions.dart';
-import 'package:monekin/core/models/account/account.dart';
-import 'package:monekin/core/models/tags/tag.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
-import 'package:monekin/core/presentation/widgets/bottomSheetFooter.dart';
-import 'package:monekin/core/presentation/widgets/count_indicator.dart';
-import 'package:monekin/core/presentation/widgets/modal_container.dart';
-import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
-import 'package:monekin/i18n/translations.g.dart';
-
-part 'transaction_more_info.modal.g.dart';
-
-@CopyWith()
-class TransactionMoreInfo {
- final String? note;
- final List tags;
-
-// Specify only if we are on a transfer:
- final num? valueInDestiny;
- final Account? transferAccount;
-
- const TransactionMoreInfo({
- this.note,
- this.tags = const [],
- this.valueInDestiny,
- this.transferAccount,
- });
-}
-
-Future showTransactionMoreInfoModal(
- BuildContext context, {
- required TransactionMoreInfo data,
-}) {
- return showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- showDragHandle: true,
- builder: (context) {
- return _TransactionMoreInfoModal(
- initialData: data,
- );
- });
-}
-
-class _TransactionMoreInfoModal extends StatefulWidget {
- const _TransactionMoreInfoModal({required this.initialData});
-
- final TransactionMoreInfo initialData;
-
- @override
- State<_TransactionMoreInfoModal> createState() =>
- _TransactionMoreInfoModalState();
-}
-
-class _TransactionMoreInfoModalState extends State<_TransactionMoreInfoModal> {
- late TransactionMoreInfo moreInfoData;
-
- @override
- void initState() {
- super.initState();
-
- moreInfoData = widget.initialData.copyWith();
- }
-
- @override
- Widget build(BuildContext context) {
- final t = Translations.of(context);
-
- var listTiles = [
- ListTile(
- leading: const Icon(Icons.location_on_outlined),
- trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
- title: const Text('Location'),
- subtitle: const Text('Coming soon!'),
- enabled: false,
- onTap: () {},
- ),
- const Divider(),
- ListTile(
- leading: Icon(Tag.icon),
- trailing: CountIndicatorWithExpandArrow(
- countToDisplay: moreInfoData.tags.length,
- ),
- title: Text(t.tags.display(n: 2)),
- subtitle: Text(
- moreInfoData.tags.isEmpty
- ? t.transaction.form.no_tags
- : moreInfoData.tags.map((t) => t.name).printFormatted(),
- style: TextStyle(
- fontStyle:
- moreInfoData.tags.isEmpty ? FontStyle.italic : FontStyle.normal,
- ),
- ),
- onTap: () {
- showTagListModal(context,
- modal: TagSelector(
- allowEmptySubmit: true,
- includeNullTag: false,
- selectedTags: moreInfoData.tags,
- )).then((value) {
- if (value != null) {
- setState(() {
- moreInfoData = moreInfoData.copyWith(
- tags: value.nonNulls.toList(),
- );
- });
- }
- });
- },
- ),
- const Divider(),
- if (moreInfoData.valueInDestiny != null) ...[
- ListTile(
- leading: const Icon(Icons.start_rounded),
- trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
- title: Text(t.transfer.form.value_in_destiny.title),
- subtitle: CurrencyDisplayer(
- amountToConvert: moreInfoData.valueInDestiny!.toDouble(),
- currency: moreInfoData.transferAccount?.currency,
- ),
- enabled: moreInfoData.transferAccount != null,
- onTap: () {
- showTransactionValueInDestinyModal(
- context,
- initialValue: moreInfoData.valueInDestiny!,
- currency: moreInfoData.transferAccount!
- .currency, // Can not be null due to the `enabled` property of the tile
- ).then((value) {
- if (value != null) {
- setState(() {
- moreInfoData = moreInfoData.copyWith(valueInDestiny: value);
- });
- }
- });
- },
- ),
- const Divider(),
- ]
- ];
-
- return DraggableScrollableSheet(
- expand: false,
- maxChildSize: 0.85,
- minChildSize: 0.85,
- initialChildSize: 0.85,
- builder: (context, sc) {
- return ModalContainer(
- title: t.transaction.details,
- body: Column(
- children: [
- SingleChildScrollView(
- controller: sc,
- child: Column(
- mainAxisSize: MainAxisSize.max,
- children: [
- TextFormField(
- initialValue: widget.initialData.note,
- onChanged: (value) {
- setState(() {
- moreInfoData = moreInfoData.copyWith(note: value);
- });
- },
- maxLines: 3,
- decoration: InputDecoration(
- border: InputBorder.none,
- fillColor: AppColors.of(context).modalBackground,
- hoverColor: AppColors.of(context).modalBackground,
- focusColor: AppColors.of(context).modalBackground,
- hintText: t.transaction.form.description_info,
- ),
- ),
- const Divider(),
- ...listTiles
- ],
- ),
- ),
- ],
- ),
- footer: BottomSheetFooter(
- onSaved: () => Navigator.pop(context, moreInfoData),
- ),
- );
- },
- );
- }
-}
diff --git a/lib/app/transactions/form/dialogs/transaction_more_info.modal.g.dart b/lib/app/transactions/form/dialogs/transaction_more_info.modal.g.dart
deleted file mode 100644
index fadd7352..00000000
--- a/lib/app/transactions/form/dialogs/transaction_more_info.modal.g.dart
+++ /dev/null
@@ -1,111 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of 'transaction_more_info.modal.dart';
-
-// **************************************************************************
-// CopyWithGenerator
-// **************************************************************************
-
-abstract class _$TransactionMoreInfoCWProxy {
- TransactionMoreInfo note(String? note);
-
- TransactionMoreInfo tags(List tags);
-
- TransactionMoreInfo valueInDestiny(num? valueInDestiny);
-
- TransactionMoreInfo transferAccount(Account? transferAccount);
-
- /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TransactionMoreInfo(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
- ///
- /// Usage
- /// ```dart
- /// TransactionMoreInfo(...).copyWith(id: 12, name: "My name")
- /// ````
- TransactionMoreInfo call({
- String? note,
- List? tags,
- num? valueInDestiny,
- Account? transferAccount,
- });
-}
-
-/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfTransactionMoreInfo.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfTransactionMoreInfo.copyWith.fieldName(...)`
-class _$TransactionMoreInfoCWProxyImpl implements _$TransactionMoreInfoCWProxy {
- const _$TransactionMoreInfoCWProxyImpl(this._value);
-
- final TransactionMoreInfo _value;
-
- @override
- TransactionMoreInfo note(String? note) => this(note: note);
-
- @override
- TransactionMoreInfo tags(List tags) => this(tags: tags);
-
- @override
- TransactionMoreInfo valueInDestiny(num? valueInDestiny) =>
- this(valueInDestiny: valueInDestiny);
-
- @override
- TransactionMoreInfo transferAccount(Account? transferAccount) =>
- this(transferAccount: transferAccount);
-
- @override
-
- /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TransactionMoreInfo(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
- ///
- /// Usage
- /// ```dart
- /// TransactionMoreInfo(...).copyWith(id: 12, name: "My name")
- /// ````
- TransactionMoreInfo call({
- Object? note = const $CopyWithPlaceholder(),
- Object? tags = const $CopyWithPlaceholder(),
- Object? valueInDestiny = const $CopyWithPlaceholder(),
- Object? transferAccount = const $CopyWithPlaceholder(),
- }) {
- return TransactionMoreInfo(
- note: note == const $CopyWithPlaceholder()
- ? _value.note
- // ignore: cast_nullable_to_non_nullable
- : note as String?,
- tags: tags == const $CopyWithPlaceholder() || tags == null
- ? _value.tags
- // ignore: cast_nullable_to_non_nullable
- : tags as List,
- valueInDestiny: valueInDestiny == const $CopyWithPlaceholder()
- ? _value.valueInDestiny
- // ignore: cast_nullable_to_non_nullable
- : valueInDestiny as num?,
- transferAccount: transferAccount == const $CopyWithPlaceholder()
- ? _value.transferAccount
- // ignore: cast_nullable_to_non_nullable
- : transferAccount as Account?,
- );
- }
-}
-
-extension $TransactionMoreInfoCopyWith on TransactionMoreInfo {
- /// Returns a callable class that can be used as follows: `instanceOfTransactionMoreInfo.copyWith(...)` or like so:`instanceOfTransactionMoreInfo.copyWith.fieldName(...)`.
- // ignore: library_private_types_in_public_api
- _$TransactionMoreInfoCWProxy get copyWith =>
- _$TransactionMoreInfoCWProxyImpl(this);
-
- /// Copies the object with the specific fields set to `null`. If you pass `false` as a parameter, nothing will be done and it will be ignored. Don't do it. Prefer `copyWith(field: null)` or `TransactionMoreInfo(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
- ///
- /// Usage
- /// ```dart
- /// TransactionMoreInfo(...).copyWithNull(firstField: true, secondField: true)
- /// ````
- TransactionMoreInfo copyWithNull({
- bool note = false,
- bool valueInDestiny = false,
- bool transferAccount = false,
- }) {
- return TransactionMoreInfo(
- note: note == true ? null : this.note,
- tags: tags,
- valueInDestiny: valueInDestiny == true ? null : this.valueInDestiny,
- transferAccount: transferAccount == true ? null : this.transferAccount,
- );
- }
-}
diff --git a/lib/app/transactions/form/dialogs/transaction_title_modal.dart b/lib/app/transactions/form/dialogs/transaction_title_modal.dart
deleted file mode 100644
index 5fa2ad51..00000000
--- a/lib/app/transactions/form/dialogs/transaction_title_modal.dart
+++ /dev/null
@@ -1,70 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:monekin/core/presentation/widgets/bottomSheetFooter.dart';
-import 'package:monekin/core/presentation/widgets/modal_container.dart';
-import 'package:monekin/core/utils/constants.dart';
-import 'package:monekin/i18n/translations.g.dart';
-
-Future showTransactionTitleModal(
- BuildContext context, {
- required String? initialTitle,
-}) {
- return showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- showDragHandle: true,
- builder: (context) {
- return TransactionTitleModal(
- initialTitle: initialTitle,
- );
- });
-}
-
-class TransactionTitleModal extends StatefulWidget {
- const TransactionTitleModal({super.key, required this.initialTitle});
-
- final String? initialTitle;
-
- @override
- State createState() => _TransactionTitleModalState();
-}
-
-class _TransactionTitleModalState extends State {
- FocusNode titleFocusNode = FocusNode();
-
- TextEditingController titleController = TextEditingController();
-
- @override
- void initState() {
- super.initState();
-
- titleController.text = widget.initialTitle ?? '';
- titleFocusNode.requestFocus();
- }
-
- @override
- void dispose() {
- titleFocusNode.dispose();
-
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- final t = Translations.of(context);
-
- return ModalContainer(
- title: t.transaction.form.title,
- bodyPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
- body: TextFormField(
- controller: titleController,
- maxLength: maxLabelLenghtForDisplayNames,
- decoration:
- InputDecoration(label: Text(t.transaction.form.title_short)),
- focusNode: titleFocusNode,
- ),
- footer: BottomSheetFooter(
- onSaved: () => Navigator.pop(context, titleController.text),
- ),
- );
- }
-}
diff --git a/lib/app/transactions/form/dialogs/transaction_value_in_destiny_modal.dart b/lib/app/transactions/form/dialogs/transaction_value_in_destiny_modal.dart
deleted file mode 100644
index 5df65998..00000000
--- a/lib/app/transactions/form/dialogs/transaction_value_in_destiny_modal.dart
+++ /dev/null
@@ -1,87 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:monekin/core/database/app_db.dart';
-import 'package:monekin/core/presentation/widgets/bottomSheetFooter.dart';
-import 'package:monekin/core/presentation/widgets/modal_container.dart';
-import 'package:monekin/core/utils/text_field_utils.dart';
-import 'package:monekin/i18n/translations.g.dart';
-
-Future showTransactionValueInDestinyModal(
- BuildContext context, {
- required num initialValue,
- required CurrencyInDB currency,
-}) {
- return showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- showDragHandle: true,
- builder: (context) {
- return TransactionValueInDestinyModal(
- initialValue: initialValue,
- currency: currency,
- );
- },
- );
-}
-
-class TransactionValueInDestinyModal extends StatefulWidget {
- const TransactionValueInDestinyModal(
- {super.key, required this.initialValue, required this.currency});
-
- final num initialValue;
- final CurrencyInDB currency;
-
- @override
- State createState() =>
- _TransactionValueInDestinyModalState();
-}
-
-class _TransactionValueInDestinyModalState
- extends State {
- FocusNode valueInputFocusNode = FocusNode();
-
- TextEditingController valueController = TextEditingController();
-
- @override
- void initState() {
- super.initState();
-
- valueController.text = widget.initialValue.toString();
- valueInputFocusNode.requestFocus();
- }
-
- @override
- void dispose() {
- valueInputFocusNode.dispose();
-
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- final t = Translations.of(context);
-
- return ModalContainer(
- title: t.transfer.form.value_in_destiny.title,
- bodyPadding:
- const EdgeInsets.only(bottom: 12, left: 16, right: 16, top: 8),
- body: TextFormField(
- controller: valueController,
- keyboardType: TextInputType.number,
- inputFormatters: decimalDigitFormatter,
- decoration: InputDecoration(
- label: Text(t.transfer.form.value_in_destiny.title),
- suffixText: widget.currency.symbol,
- ),
- focusNode: valueInputFocusNode,
- onChanged: (value) {
- setState(() {});
- },
- ),
- footer: BottomSheetFooter(
- onSaved: double.tryParse(valueController.text) == null
- ? null
- : () => Navigator.pop(context, double.parse(valueController.text)),
- ),
- );
- }
-}
diff --git a/lib/app/transactions/form/transaction_form.page.dart b/lib/app/transactions/form/transaction_form.page.dart
index 96785e58..0a952218 100644
--- a/lib/app/transactions/form/transaction_form.page.dart
+++ b/lib/app/transactions/form/transaction_form.page.dart
@@ -3,17 +3,13 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:monekin/app/accounts/account_selector.dart';
import 'package:monekin/app/categories/selectors/category_picker.dart';
-import 'package:monekin/app/transactions/form/dialogs/transaction_more_info.modal.dart';
+import 'package:monekin/app/transactions/form/dialogs/amount_selector.dart';
import 'package:monekin/app/transactions/form/dialogs/transaction_status_selector.dart';
-import 'package:monekin/app/transactions/form/dialogs/transaction_title_modal.dart';
-import 'package:monekin/app/transactions/form/dialogs/transaction_value_in_destiny_modal.dart';
-import 'package:monekin/app/transactions/form/widgets/transaction_form_calculator.dart';
import 'package:monekin/core/database/app_db.dart';
import 'package:monekin/core/database/services/account/account_service.dart';
-import 'package:monekin/core/database/services/currency/currency_service.dart';
import 'package:monekin/core/database/services/exchange-rate/exchange_rate_service.dart';
import 'package:monekin/core/database/services/transaction/transaction_service.dart';
-import 'package:monekin/core/extensions/string.extension.dart';
+import 'package:monekin/core/extensions/color.extensions.dart';
import 'package:monekin/core/models/account/account.dart';
import 'package:monekin/core/models/category/category.dart';
import 'package:monekin/core/models/supported-icon/icon_displayer.dart';
@@ -21,26 +17,41 @@ import 'package:monekin/core/models/tags/tag.dart';
import 'package:monekin/core/models/transaction/recurrency_data.dart';
import 'package:monekin/core/models/transaction/transaction.dart';
import 'package:monekin/core/models/transaction/transaction_status.enum.dart';
-import 'package:monekin/core/models/transaction/transaction_type.enum.dart';
-import 'package:monekin/core/presentation/animations/shake/shake_widget.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
-import 'package:monekin/core/presentation/widgets/modal_container.dart';
-import 'package:monekin/core/presentation/widgets/monekin_popup_menu_button.dart';
+import 'package:monekin/core/presentation/animations/animated_expanded.dart';
+import 'package:monekin/core/presentation/animations/scaled_animated_switcher.dart';
+import 'package:monekin/core/presentation/animations/shake_widget.dart';
+import 'package:monekin/core/presentation/responsive/breakpoint_container.dart';
+import 'package:monekin/core/presentation/responsive/breakpoints.dart';
+import 'package:monekin/core/presentation/widgets/inline_info_card.dart';
import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart';
+import 'package:monekin/core/presentation/widgets/persistent_footer_button.dart';
+import 'package:monekin/core/presentation/widgets/tappable.dart';
+import 'package:monekin/core/utils/constants.dart';
import 'package:monekin/core/utils/date_time_picker.dart';
+import 'package:monekin/core/utils/focus.dart';
+import 'package:monekin/core/utils/text_field_utils.dart';
import 'package:monekin/core/utils/uuid.dart';
import 'package:monekin/i18n/translations.g.dart';
-import 'widgets/account_or_category_selector.dart';
+import '../../../core/models/transaction/transaction_type.enum.dart';
+import '../../tags/tags_selector.modal.dart';
-enum TransactionFormMode { transfer, incomeOrExpense }
+openTransactionFormDialog(BuildContext context, TransactionFormPage widget) {
+ return showDialog(
+ context: context,
+ builder: (context) {
+ return widget;
+ });
+}
class TransactionFormPage extends StatefulWidget {
- const TransactionFormPage(
- {super.key,
- this.mode = TransactionType.E,
- this.fromAccount,
- this.transactionToEdit});
+ const TransactionFormPage({
+ super.key,
+ this.mode = TransactionType.E,
+ this.fromAccount,
+ this.transactionToEdit,
+ });
+
final TransactionType mode;
final MoneyTransaction? transactionToEdit;
@@ -51,29 +62,40 @@ class TransactionFormPage extends StatefulWidget {
State createState() => _TransactionFormPageState();
}
-class _TransactionFormPageState extends State {
- late TransactionType transactionType;
- final _shakeKey = GlobalKey();
+class _TransactionFormPageState extends State
+ with TickerProviderStateMixin {
+ final _formKey = GlobalKey();
- Account? fromAccount;
+ double transactionValue = 0;
+
+ TextEditingController valueInDestinyController = TextEditingController();
+ double? get valueInDestinyToNumber =>
+ double.tryParse(valueInDestinyController.text);
Category? selectedCategory;
- DateTime date = DateTime.now();
+ Account? fromAccount;
+ Account? transferAccount;
- double transactionAmount = 0;
+ DateTime date = DateTime.now();
TransactionStatus? status;
- String? title;
+ TextEditingController notesController = TextEditingController();
+ TextEditingController titleController = TextEditingController();
bool get isEditMode => widget.transactionToEdit != null;
RecurrencyData recurrentRule = const RecurrencyData.noRepeat();
- List get tags => moreInfo.tags;
+ List tags = [];
+
+ late TransactionType transactionType;
+
+ final _shakeKey = GlobalKey();
- TransactionMoreInfo moreInfo = const TransactionMoreInfo();
+ late TabController _tabController;
+ final _mainContainerRadius = 12.0;
@override
void initState() {
@@ -81,38 +103,110 @@ class _TransactionFormPageState extends State {
transactionType = widget.mode;
- if (transactionType == TransactionType.E) {
- transactionAmount = transactionAmount * -1;
- }
+ _tabController = TabController(
+ length: 3,
+ initialIndex: transactionType.index,
+ vsync: this,
+ );
+
+ _tabController.addListener(() {
+ transactionType = TransactionType.values.elementAt(_tabController.index);
+
+ if (transactionType.isTransfer && transactionValue.isNegative) {
+ transactionValue = transactionValue * -1;
+ }
+
+ setState(() {});
+ });
if (widget.transactionToEdit != null) {
fillForm(widget.transactionToEdit!);
- } else {
- AccountService.instance
- .getAccounts(
- predicate: (acc, curr) => AppDB.instance.buildExpr([
- acc.type.equalsValue(AccountType.saving).not(),
- acc.closingDate.isNull()
- ]),
- limit: transactionType.isTransfer ? 2 : 1)
- .first
- .then((acc) {
- fromAccount = widget.fromAccount ?? acc[0];
-
- if (transactionType.isTransfer) {
- moreInfo = moreInfo.copyWith(
- transferAccount: acc[1].id != fromAccount!.id ? acc[1] : acc[0],
- );
- }
- setState(() {});
- });
+ return;
}
+
+ AccountService.instance
+ .getAccounts(
+ predicate: (acc, curr) => AppDB.instance.buildExpr([
+ acc.type.equalsValue(AccountType.saving).not(),
+ acc.closingDate.isNull()
+ ]),
+ limit: transactionType.isTransfer ? 2 : 1,
+ )
+ .first
+ .then((acc) {
+ fromAccount = widget.fromAccount ?? acc[0];
+
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ displayAmountModal(context);
+ });
+
+ if (widget.mode.isTransfer) {
+ transferAccount = (acc[1].id != fromAccount!.id ? acc[1] : acc[0]);
+ }
+
+ setState(() {});
+ });
+ }
+
+ Widget selector(
+ {required String title,
+ required String? inputValue,
+ required Widget icon,
+ required Function onClick,
+ required BorderRadius? borderRadius}) {
+ final t = Translations.of(context);
+
+ return InkWell(
+ onTap: () {
+ unfocusCurrentFocusedItem(context);
+ onClick();
+ },
+ borderRadius: borderRadius,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ icon,
+ const SizedBox(width: 12),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ title,
+ softWrap: false,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.labelMedium!.copyWith(
+ fontWeight: FontWeight.w300,
+ ),
+ ),
+ Text(
+ inputValue ?? t.general.unspecified,
+ softWrap: false,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.titleMedium!.copyWith(
+ fontWeight: FontWeight.w600,
+ ),
+ )
+ ],
+ ),
+ )
+ ],
+ ),
+ ),
+ );
}
submitForm() {
+ print("HOLAALLALA");
+ print(transactionType.isTransfer);
+ print(transferAccount);
if (transactionType.isIncomeOrExpense && selectedCategory == null ||
- transactionType.isTransfer && moreInfo.transferAccount == null) {
+ transactionType.isTransfer && transferAccount == null) {
_shakeKey.currentState?.shake();
return;
}
@@ -120,7 +214,7 @@ class _TransactionFormPageState extends State {
final t = Translations.of(context);
final scMessenger = ScaffoldMessenger.of(context);
- if (transactionAmount == 0) {
+ if (transactionValue == 0) {
scMessenger.showSnackBar(
SnackBar(content: Text(t.transaction.form.validators.zero)),
);
@@ -128,7 +222,7 @@ class _TransactionFormPageState extends State {
return;
}
- if (transactionAmount < 0 && transactionType.isTransfer) {
+ if (transactionValue < 0 && transactionType.isTransfer) {
scMessenger.showSnackBar(SnackBar(
content: Text(t.transaction.form.validators.negative_transfer),
));
@@ -136,33 +230,16 @@ class _TransactionFormPageState extends State {
return;
}
- // Transactions after account creation:
if (fromAccount != null && fromAccount!.date.compareTo(date) > 0) {
scMessenger.showSnackBar(
SnackBar(
- content:
- Text(t.transaction.form.validators.date_after_account_creation),
- ),
- );
-
- return;
- }
-
- // In transfers, we can not have the same source and destination account:
- if (transactionType.isTransfer &&
- fromAccount!.id == moreInfo.transferAccount!.id) {
- scMessenger.showSnackBar(
- SnackBar(
- content: Text(
- t.transaction.form.validators.transfer_between_same_accounts),
- ),
+ content: Text(
+ t.transaction.form.validators.date_after_account_creation)),
);
return;
}
- // VALIDATIONS ENDED -- PROCEED:
-
onSuccess() {
Navigator.pop(context);
@@ -179,28 +256,29 @@ class _TransactionFormPageState extends State {
TransactionInDB(
id: newTrID,
date: date,
+ type: transactionType,
accountID: fromAccount!.id,
- value: transactionAmount,
+ value: transactionType.isIncomeOrExpense &&
+ selectedCategory!.type.isExpense
+ ? transactionValue * -1
+ : transactionValue,
isHidden: false,
- type: transactionType,
status: date.compareTo(DateTime.now()) > 0
? TransactionStatus.pending
: status,
- notes: moreInfo.note.notEmptyString,
- title: title.notEmptyString,
+ notes: notesController.text.isEmpty ? null : notesController.text,
+ title: titleController.text.isEmpty ? null : titleController.text,
intervalEach: recurrentRule.intervalEach,
intervalPeriod: recurrentRule.intervalPeriod,
endDate: recurrentRule.ruleRecurrentLimit?.endDate,
remainingTransactions:
recurrentRule.ruleRecurrentLimit?.remainingIterations,
- valueInDestiny: transactionType.isTransfer &&
- moreInfo.valueInDestiny != transactionAmount
- ? moreInfo.valueInDestiny?.toDouble()
- : null,
+ valueInDestiny:
+ transactionType.isTransfer ? valueInDestinyToNumber : null,
categoryID:
transactionType.isIncomeOrExpense ? selectedCategory?.id : null,
receivingAccountID:
- transactionType.isTransfer ? moreInfo.transferAccount?.id : null,
+ transactionType.isTransfer ? transferAccount?.id : null,
),
)
.then((value) {
@@ -230,142 +308,6 @@ class _TransactionFormPageState extends State {
});
}
- fillForm(MoneyTransaction transaction) async {
- setState(
- () {
- fromAccount = transaction.account;
- date = transaction.date;
- status = transaction.status;
- selectedCategory = transaction.category;
- recurrentRule = transaction.recurrentInfo;
-
- if (selectedCategory != null &&
- selectedCategory!.type == CategoryType.B) {
- if (transaction.value < 0) {
- selectedCategory!.type = CategoryType.E;
- } else {
- selectedCategory!.type = CategoryType.I;
- }
- }
-
- moreInfo = TransactionMoreInfo(
- tags: transaction.tags,
- note: transaction.notes,
- valueInDestiny: transaction.isTransfer
- ? transaction.valueInDestiny ?? transaction.value
- : null,
- transferAccount: transaction.receivingAccount,
- );
- },
- );
-
- title = transaction.title;
- transactionAmount = transaction.value;
- }
-
- Card buildAccoutAndCategorySelectorRow(BuildContext context) {
- return Card(
- shape: RoundedRectangleBorder(
- // side: BorderSide(color: Theme.of(context).dividerColor, width: 2),
- borderRadius: BorderRadius.circular(12),
- ),
- margin: const EdgeInsets.all(0),
- elevation: 0,
- color: AppColors.of(context).surface,
- clipBehavior: Clip.hardEdge,
- child: LayoutBuilder(builder: (context, constraints) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Flexible(
- child: ConstrainedBox(
- constraints:
- BoxConstraints(maxWidth: constraints.maxWidth * 0.5),
- child: AccountOrCategorySelector(
- title: t.general.account,
- inputValue: fromAccount?.name,
- icon: fromAccount?.displayIcon(context) ??
- IconDisplayer(
- displayMode: IconDisplayMode.polygon,
- icon: Icons.question_mark_rounded,
- mainColor: AppColors.of(context).primary,
- ),
- onClick: () async {
- final modalRes = await showAccountSelector(fromAccount!);
-
- if (modalRes != null && modalRes.isNotEmpty) {
- setState(() {
- fromAccount = modalRes.first;
- });
- }
- }),
- ),
- ),
- const Padding(
- padding: EdgeInsets.symmetric(vertical: 0, horizontal: 4),
- child: Icon(Icons.arrow_forward, size: 16),
- ),
- if (transactionType == TransactionType.T)
- Flexible(
- child: ShakeWidget(
- duration: const Duration(milliseconds: 200),
- shakeCount: 1,
- shakeOffset: 10,
- key: _shakeKey,
- child: ConstrainedBox(
- constraints:
- BoxConstraints(maxWidth: constraints.maxWidth * 0.5),
- child: AccountOrCategorySelector(
- title: t.transfer.form.to,
- inputValue: moreInfo.transferAccount?.name,
- icon: moreInfo.transferAccount?.displayIcon(context) ??
- IconDisplayer(
- displayMode: IconDisplayMode.polygon,
- icon: Icons.question_mark_rounded,
- mainColor: AppColors.of(context).primary,
- ),
- onClick: () async {
- final modalRes = await showAccountSelector(
- moreInfo.transferAccount);
-
- if (modalRes != null && modalRes.isNotEmpty) {
- moreInfo = moreInfo.copyWith(
- transferAccount: modalRes.first);
- setState(() {});
- }
- }),
- ),
- ),
- ),
- if (transactionType != TransactionType.T)
- Flexible(
- child: ShakeWidget(
- duration: const Duration(milliseconds: 200),
- shakeCount: 1,
- shakeOffset: 10,
- key: _shakeKey,
- child: ConstrainedBox(
- constraints:
- BoxConstraints(maxWidth: constraints.maxWidth * 0.5),
- child: AccountOrCategorySelector(
- title: t.general.category,
- inputValue: selectedCategory?.name,
- icon: IconDisplayer.fromCategory(
- context,
- category: selectedCategory ??
- Category.fromDB(Category.unkown(), null),
- size: 24,
- ),
- onClick: () => selectCategory()),
- ),
- ),
- ),
- ],
- );
- }),
- );
- }
-
Future?> showAccountSelector(Account? account) {
return showAccountSelectorBottomSheet(
context,
@@ -382,11 +324,9 @@ class _TransactionFormPageState extends State {
context,
modal: CategoryPicker(
selectedCategory: selectedCategory,
- categoryType: [
- CategoryType.B,
- if (transactionType == TransactionType.E) CategoryType.E,
- if (transactionType == TransactionType.I) CategoryType.I
- ],
+ categoryType: transactionType == TransactionType.E
+ ? [CategoryType.E]
+ : [CategoryType.I],
),
);
@@ -397,430 +337,616 @@ class _TransactionFormPageState extends State {
}
}
+ fillForm(MoneyTransaction transaction) async {
+ setState(() {
+ fromAccount = transaction.account;
+ transferAccount = transaction.receivingAccount;
+ date = transaction.date;
+ status = transaction.status;
+ selectedCategory = transaction.category;
+ recurrentRule = transaction.recurrentInfo;
+
+ if (selectedCategory != null &&
+ selectedCategory!.type == CategoryType.B) {
+ if (transaction.value < 0) {
+ selectedCategory!.type = CategoryType.E;
+ } else {
+ selectedCategory!.type = CategoryType.I;
+ }
+ }
+
+ tags = [...transaction.tags];
+ });
+
+ notesController.text = transaction.notes ?? '';
+ titleController.text = transaction.title ?? '';
+ transactionValue = transaction.value;
+ transactionType = transaction.type;
+
+ if (transactionType == TransactionType.E) {
+ transactionValue = transactionValue * -1;
+ }
+
+ valueInDestinyController.text =
+ transaction.valueInDestiny?.abs().toString() ?? '';
+ }
+
+ Widget buildValueInDestinyFormField() {
+ return ListTile(
+ leading: const Icon(Icons.trending_flat_rounded),
+ minTileHeight: 64,
+ title: TextFormField(
+ controller: valueInDestinyController,
+ decoration: InputDecoration(
+ border: InputBorder.none,
+ hintText: t.transfer.form.value_in_destiny.title,
+ suffixText: transferAccount?.currency.symbol,
+ filled: false,
+ isDense: false,
+ ),
+ keyboardType: TextInputType.number,
+ inputFormatters: decimalDigitFormatter,
+ validator: (value) {
+ final defaultNumberValidatorResult = fieldValidator(value,
+ isRequired: false, validator: ValidatorType.double);
+
+ if (defaultNumberValidatorResult != null) {
+ return defaultNumberValidatorResult;
+ }
+
+ return null;
+ },
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ textInputAction: TextInputAction.next,
+ onChanged: (value) {
+ setState(() {});
+ },
+ ),
+ );
+ }
+
+ Widget buildStatusSelector() {
+ final isSelectorDisabled = date.compareTo(DateTime.now()) > 0;
+
+ final selectedStatus =
+ isSelectorDisabled ? TransactionStatus.pending : status;
+
+ return ListTile(
+ leading: ScaledAnimatedSwitcher(
+ keyToWatch: selectedStatus.icon.toString(),
+ child: Icon(
+ selectedStatus.icon,
+ color:
+ (selectedStatus?.color ?? Theme.of(context).colorScheme.primary)
+ .withOpacity(isSelectorDisabled ? 0.3 : 1),
+ ),
+ ),
+ minTileHeight: 64,
+ title: Text(selectedStatus.displayName(context)),
+ enabled: !isSelectorDisabled,
+ onTap: () {
+ unfocusCurrentFocusedItem(context);
+
+ showTransactioStatusModal(context, initialStatus: status)
+ .then((modalRes) {
+ if (modalRes == null) return;
+
+ setState(() {
+ status = modalRes.result;
+ });
+ });
+ },
+ );
+ }
+
+ Widget buildRecurrencySelectorField() {
+ return ListTile(
+ leading: Icon(
+ recurrentRule.isRecurrent ? Icons.repeat_rounded : Icons.repeat_one,
+ ),
+ minTileHeight: 64,
+ title: Text(recurrentRule.formText(context)),
+ onTap: () {
+ showIntervalSelectoHelpDialog(context,
+ selectedRecurrentRule: recurrentRule,
+ onRecurrentRuleSelected: (res) {
+ setState(() {
+ recurrentRule = res;
+ });
+ });
+ },
+ );
+ }
+
+ Widget buildDescriptionField() {
+ return ListTile(
+ leading: const Icon(Icons.description_rounded),
+ minTileHeight: 64,
+ titleAlignment: ListTileTitleAlignment.titleHeight,
+ title: TextFormField(
+ controller: notesController,
+ minLines: 2,
+ maxLines: 10,
+ decoration: InputDecoration(
+ isDense: false,
+ filled: false,
+ border: InputBorder.none,
+ hintText: t.transaction.form.description_info,
+ ),
+ ),
+ );
+ }
+
+ Widget buildTitleField() {
+ return ListTile(
+ leading: const Icon(Icons.title_rounded),
+ title: TextFormField(
+ controller: titleController,
+ maxLength: maxLabelLenghtForDisplayNames,
+ decoration: InputDecoration(
+ isDense: false,
+ filled: false,
+ counterText: '',
+ border: InputBorder.none,
+ hintText: t.transaction.form.title,
+ ),
+ ),
+ );
+ }
+
@override
Widget build(BuildContext context) {
+ final t = Translations.of(context);
+
+ final formFieldWithDividers = [
+ buildTitleField(),
+ const Divider(),
+ buildTransactionDateSelector(),
+ const Divider(),
+ buildRecurrencySelectorField(),
+ const Divider(),
+ buildStatusSelector(),
+ const Divider(),
+ buildTransactionTagsSelector(),
+ const Divider(),
+ if (transactionType.isTransfer) ...[
+ buildValueInDestinyFormField(),
+ const Divider(),
+ ],
+ buildDescriptionField(),
+ const Divider(),
+ ];
+
return Scaffold(
- resizeToAvoidBottomInset: false,
appBar: AppBar(
- centerTitle: true,
- title: SegmentedButton(
- style: SegmentedButton.styleFrom(
- selectedBackgroundColor: transactionType.color(context),
- selectedForegroundColor: Colors.white,
+ title: Text(
+ isEditMode
+ ? t.transaction.edit
+ : transactionType == TransactionType.T
+ ? t.transfer.create
+ : transactionType == TransactionType.E
+ ? t.transaction.new_expense
+ : t.transaction.new_income,
+ ),
+ backgroundColor: transactionType.color(context).withOpacity(0.85),
+ foregroundColor: Colors.white,
+ elevation: 0,
+ bottom: TabBar(
+ indicatorColor: Colors.white,
+ labelColor: Colors.white,
+ labelStyle: const TextStyle(fontWeight: FontWeight.w700),
+ unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.normal),
+ unselectedLabelColor: Colors.white.withOpacity(0.8),
+ tabAlignment: TabAlignment.fill,
+ dividerColor: transactionType.color(context).darken(0.3),
+ controller: _tabController,
+ tabs: TransactionType.values
+ .map((tType) => Tab(
+ text: tType.displayName(context),
+ ))
+ .toList(),
+ isScrollable: false,
+ ),
+ ),
+ persistentFooterButtons: [
+ PersistentFooterButton(
+ child: FilledButton.icon(
+ onPressed: () {
+ if (_formKey.currentState!.validate()) {
+ _formKey.currentState!.save();
+
+ submitForm();
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text(t.general.validations.form_error)),
+ );
+ }
+ },
+ icon: const Icon(Icons.save),
+ label: Text(isEditMode ? t.transaction.edit : t.transaction.create),
),
- showSelectedIcon: false,
- segments: TransactionType.values
- .map(
- (e) => ButtonSegment(
- value: e,
- icon: Icon(e.mathIcon),
- tooltip: e.displayName(context),
+ )
+ ],
+ body: Form(
+ key: _formKey,
+ child: BreakpointContainer(
+ lgChild: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Expanded(
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ buildAmountContainer(context),
+ buildAccoutAndCategorySelectorRow(context),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ const VerticalDivider(thickness: 2),
+ Expanded(
+ child: Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 0, vertical: 16),
+ child: Column(
+ children: formFieldWithDividers,
+ ),
+ ),
+ ),
+ ],
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ buildAmountContainer(context),
+ buildAccoutAndCategorySelectorRow(context),
+
+ // const Divider(),
+ Expanded(
+ child: SingleChildScrollView(
+ padding: const EdgeInsets.only(top: 4, bottom: 12),
+ child: Column(
+ children: formFieldWithDividers,
+ ),
),
)
- .toList(),
- selected: {transactionType},
- onSelectionChanged: (newMode) {
- transactionType = newMode.first;
-
- if (transactionType == TransactionType.E &&
- !transactionAmount.isNegative) {
- transactionAmount = transactionAmount * -1;
- } else if (transactionType != TransactionType.E &&
- transactionAmount.isNegative) {
- // Transfers and incomes -> Convert to positive
- transactionAmount = transactionAmount * -1;
- }
-
- setState(() {});
- },
+ ],
+ ),
),
- actions: [
- MonekinPopupMenuButton(actionItems: [
- //TODO
- ])
- ],
),
- body: Column(
- children: [
- Expanded(
- child: Padding(
- padding: const EdgeInsets.fromLTRB(16, 8, 16, 12),
- child: Column(
+ );
+ }
+
+ void displayAmountModal(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ showDragHandle: true,
+ builder: (context) => AmountSelector(
+ title: t.transaction.form.value,
+ initialAmount: transactionValue,
+ enableSignToggleButton: transactionType.isIncomeOrExpense,
+ currency: fromAccount?.currency,
+ onSubmit: (amount) {
+ setState(() {
+ transactionValue = amount;
+ Navigator.pop(context);
+ });
+ },
+ ),
+ );
+ }
+
+ Widget buildAmountContainer(BuildContext context) {
+ return Tappable(
+ bgColor: transactionType.color(context).withOpacity(0.85),
+ onTap: () => displayAmountModal(context),
+ borderRadius: BreakPoint.of(context).isLargerThan(BreakpointID.md)
+ ? BorderRadius.only(
+ topLeft: Radius.circular(_mainContainerRadius),
+ topRight: Radius.circular(_mainContainerRadius),
+ )
+ : null,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20),
+ child: DefaultTextStyle(
+ style: const TextStyle(color: Colors.white),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Expanded(
- child: StreamBuilder(
- stream:
- CurrencyService.instance.getUserPreferredCurrency(),
- builder: (context, snapshot) {
- return Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- buildDatePickerButtons(context),
- const SizedBox(height: 8),
- AnimatedDefaultTextStyle(
- duration: const Duration(milliseconds: 250),
- style: Theme.of(context)
- .textTheme
- .headlineLarge!
- .copyWith(
- fontWeight: FontWeight.w800,
- color: transactionType.color(context),
- fontSize: transactionAmount.abs() >= 1000
- ? transactionAmount.abs() >= 1000000
- ? 32
- : 42
- : 56,
- ),
- child: CurrencyDisplayer(
- amountToConvert: transactionAmount,
- followPrivateMode: false,
- currency: fromAccount?.currency,
- ),
- ),
- if (fromAccount != null &&
- snapshot.hasData &&
- snapshot.data!.code !=
- fromAccount!.currency.code)
- // Display exchange rate to preferred currency if needed
- buildTrAmountInPrefCurrency()
- ],
- );
- },
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 200),
+ child: IconDisplayer(
+ key: ValueKey(transactionType.mathIcon.toString()),
+ mainColor: transactionType.color(context),
+ secondaryColor: Colors.white,
+ padding: 2,
+ borderRadius: 4,
+ icon: transactionType.mathIcon,
),
),
- Padding(
- padding: const EdgeInsets.only(left: 4, bottom: 8),
+ AnimatedDefaultTextStyle(
+ style: Theme.of(context).textTheme.headlineLarge!.copyWith(
+ fontSize: transactionValue >= 1000
+ ? transactionValue >= 1000000
+ ? 28
+ : 34
+ : 38),
+ duration: const Duration(milliseconds: 200),
child: Builder(builder: (context) {
- final buttonStyle = TextButton.styleFrom(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8),
- side: BorderSide(
- width: 2,
- color: AppColors.of(context).brand,
- ),
- ),
- iconColor: AppColors.of(context).brand,
- foregroundColor: AppColors.of(context).brand,
- backgroundColor:
- AppColors.of(context).brand.withOpacity(0.2),
+ const bigTextStyle = TextStyle(
+ fontWeight: FontWeight.w800,
+ color: Colors.white,
);
- final _valueInDestinyOrDefault =
- moreInfo.valueInDestiny ?? transactionAmount;
-
- return Row(
- // mainAxisAlignment: MainAxisAlignment.center,
- children: [
- if (transactionType.isTransfer &&
- moreInfo.transferAccount != null &&
- (moreInfo.valueInDestiny != transactionAmount &&
- transactionAmount > 0 &&
- moreInfo.valueInDestiny != null ||
- moreInfo.transferAccount!.currencyId !=
- fromAccount?.currencyId))
- TextButton.icon(
- style: buttonStyle,
- onPressed: () {
- showTransactionValueInDestinyModal(
- context,
- initialValue: _valueInDestinyOrDefault,
- currency:
- moreInfo.transferAccount!.currency,
- ).then((value) {
- if (value != null) {
- setState(() {
- moreInfo = moreInfo.copyWith(
- valueInDestiny: value,
- );
- });
- }
- });
- },
- icon: const Icon(Icons.start_rounded),
- label: Text('${NumberFormat.currency(
- symbol:
- moreInfo.transferAccount!.currency.symbol,
- decimalDigits:
- _valueInDestinyOrDefault % 1 == 0 ? 0 : 2,
- ).format(_valueInDestinyOrDefault)} a cuenta de destino')),
- if (transactionType == TransactionType.E &&
- !transactionAmount.isNegative ||
- transactionType == TransactionType.I &&
- transactionAmount.isNegative)
- TextButton.icon(
- style: buttonStyle,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- showDragHandle: true,
- builder: (context) {
- return ModalContainer(
- title: t.transaction.reversed.title,
- titleBuilder: (title) {
- return Row(
- children: [
- Icon(
- MoneyTransaction.reversedIcon,
- size: 28,
- ),
- const SizedBox(width: 12),
- Text(title)
- ],
- );
- },
- bodyPadding: const EdgeInsets.only(
- left: 16,
- bottom: 8,
- right: 8,
- ),
- body: Text(
- transactionType == TransactionType.I
- ? t.transaction.reversed
- .description_for_incomes
- : t.transaction.reversed
- .description_for_expenses),
- );
- },
- );
- },
- label: Text(t.transaction.reversed.title_short),
- icon: Icon(MoneyTransaction.reversedIcon),
- ),
- ],
+ return CurrencyDisplayer(
+ amountToConvert: transactionValue,
+ currency: fromAccount?.currency,
+ currencyStyle: bigTextStyle,
+ integerStyle: bigTextStyle,
+ followPrivateMode: false,
);
}),
),
- Column(
- children: [
- buildAccoutAndCategorySelectorRow(context),
- const Divider(height: 4),
- const SizedBox(height: 8),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Expanded(child: buildTitleButton(context)),
- const SizedBox(width: 12),
- buildExtraInfoButtons(context)
- ],
- ),
- ],
- )
],
),
- ),
+ if (fromAccount != null)
+ StreamBuilder(
+ stream: ExchangeRateService.instance
+ .calculateExchangeRateToPreferredCurrency(
+ fromCurrency: fromAccount!.currency.code,
+ amount: transactionValue,
+ ),
+ builder: (context, exchangeRateSnapshot) {
+ final shouldHide = !exchangeRateSnapshot.hasData ||
+ exchangeRateSnapshot.data! == transactionValue;
+
+ final valueInPrefCurrencyIndicator = Column(
+ children: [
+ const SizedBox(height: 4),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ const Icon(
+ Icons.swap_horizontal_circle_rounded,
+ size: 14,
+ ),
+ const SizedBox(width: 4),
+ CurrencyDisplayer(
+ amountToConvert: exchangeRateSnapshot.data ?? 0,
+ integerStyle: const TextStyle(
+ fontWeight: FontWeight.w300,
+ color: Colors.white,
+ ),
+ followPrivateMode: false,
+ ),
+ ],
+ ),
+ ],
+ );
+
+ return AnimatedSizeSwitcher(
+ duration: const Duration(milliseconds: 400),
+ child: !shouldHide
+ ? valueInPrefCurrencyIndicator
+ : const SizedBox.shrink(),
+ );
+ })
+ ],
),
- Container(
- height: 275,
- padding: const EdgeInsets.all(0),
- decoration: BoxDecoration(
- color: AppColors.of(context).light,
- ),
- child: TransactionFormCalculator(
- amountToConvert: transactionAmount,
- showNegativeToggleButton: transactionType.isIncomeOrExpense,
- onSubmit: () => submitForm(),
- onChange: (amount) {
- setState(() {
- transactionAmount = amount;
- });
- },
- ),
- )
- ],
+ ),
),
);
}
- /// Build an indicator with the transaction value in the currency of the user
- StreamBuilder buildTrAmountInPrefCurrency() {
- return StreamBuilder(
- stream:
- ExchangeRateService.instance.calculateExchangeRateToPreferredCurrency(
- fromCurrency: fromAccount!.currency.code,
- amount: transactionAmount,
- ),
- builder: (context, exchangeRateSnapshot) {
- if (!exchangeRateSnapshot.hasData ||
- exchangeRateSnapshot.data! == transactionAmount) {
- return const SizedBox.shrink();
- }
-
- return Column(
- children: [
- const SizedBox(height: 8),
- CurrencyDisplayer(
- amountToConvert: exchangeRateSnapshot.data!,
- followPrivateMode: false,
- integerStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
- color: AppColors.of(context).onSurface.withAlpha(200)),
- ),
- ],
+ Widget buildTransactionTagsSelector() {
+ final Widget tagsChips = Wrap(
+ spacing: 6,
+ runSpacing: 0,
+ children: List.generate(tags.length, (index) {
+ final tag = tags[index];
+
+ return FilterChip(
+ label: Text(
+ tag.name,
+ style: TextStyle(color: tag.colorData),
+ ),
+ selected: true,
+ onSelected: (value) => setState(() {
+ tags.removeWhere((element) => element.id == tag.id);
+ }),
+ showCheckmark: false,
+ selectedColor: tag.colorData.lighten(0.8),
+ avatar: tag.displayIcon(),
);
- },
+ }),
);
- }
- Row buildExtraInfoButtons(BuildContext context) {
- final t = Translations.of(context);
+ return ListTile(
+ leading: Icon(Tag.icon),
+ minTileHeight: 64,
+ onTap: () {
+ showTagListModal(context,
+ modal: TagSelector(
+ selectedTags: tags,
+ allowEmptySubmit: true,
+ includeNullTag: false,
+ )).then(
+ (value) {
+ if (value == null) {
+ return;
+ }
- return Row(
- children: [
- Builder(builder: (context) {
- final selectedStatus = date.compareTo(DateTime.now()) > 0
- ? TransactionStatus.pending
- : status;
-
- return IconButton.outlined(
- icon: Icon(
- selectedStatus.icon,
- color: (selectedStatus?.color ?? AppColors.of(context).primary)
- .withOpacity(date.compareTo(DateTime.now()) > 0 ? 0.3 : 1),
- ),
- tooltip: t.transaction.status.display_long,
- onPressed: date.compareTo(DateTime.now()) > 0
- ? null
- : () => showTransactioStatusModal(
- context,
- initialStatus: selectedStatus,
- ).then((modalRes) {
- if (modalRes == null) return;
-
- setState(() {
- status = modalRes.result;
- });
- }),
+ setState(() {
+ tags = value.nonNulls.toList();
+ });
+ },
);
- }),
- const SizedBox(width: 6),
- IconButton.outlined(
- onPressed: () => showTransactionMoreInfoModal(
- context,
- data: moreInfo.copyWith(
- valueInDestiny: transactionType.isTransfer
- ? moreInfo.valueInDestiny ?? transactionAmount
- : null,
- ),
- ).then((modalRes) {
- if (modalRes == null) return;
-
- setState(() {
- moreInfo = modalRes;
- });
- }),
- icon: const Icon(Icons.notes_rounded),
- ),
- ],
- );
+ },
+ title: tags.isEmpty
+ ? Text(
+ t.tags.select.title,
+ style: TextStyle(
+ color:
+ Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
+ ),
+ )
+ : tagsChips);
}
- Widget buildDatePickerButtons(BuildContext context) {
- final buttonStyle = TextButton.styleFrom(
- backgroundColor: AppColors.of(context).light,
- );
+ Widget buildTransactionDateSelector() {
+ final dateFormat = date.year == currentYear
+ ? DateFormat.MMMMd().add_jm()
+ : DateFormat.yMMMd().add_jm();
- return Row(
- mainAxisAlignment: MainAxisAlignment.center,
+ return Column(
children: [
- TextButton(
- style: buttonStyle,
- onPressed: () {
- openDateTimePicker(
+ ListTile(
+ leading: const Icon(Icons.event),
+ minTileHeight: 64,
+ title: Text(dateFormat.format(date)),
+ onTap: () async {
+ unfocusCurrentFocusedItem(context);
+
+ final datePickerRes = await openDateTimePicker(
context,
- showTimePickerAfterDate: false,
initialDate: date,
- ).then((res) {
- if (res == null) return;
+ showTimePickerAfterDate: true,
+ );
+ if (datePickerRes != null) {
setState(() {
- date = date.copyWith(
- day: res.day,
- year: res.year,
- month: res.month,
- isUtc: res.isUtc,
- );
+ date = datePickerRes;
});
- });
+ }
},
- child: Text(
- DateFormat.yMMMd().format(date),
- ),
),
- const SizedBox(width: 4),
- TextButton(
- style: buttonStyle,
- onPressed: () {
- showTimePicker(
- context: context,
- initialTime: TimeOfDay.fromDateTime(date),
- ).then((res) {
- if (res == null) return;
-
- setState(() {
- date = date.copyWith(
- hour: res.hour,
- minute: res.minute,
- );
- });
- });
- },
- child: Text(
- DateFormat.Hm().format(date),
+ if (date.compareTo(DateTime.now()) > 0)
+ InlineInfoCard(
+ margin: const EdgeInsets.fromLTRB(12, 8, 12, 16),
+ text: t.transaction.form.validators.date_max,
+ mode: InlineInfoCardMode.info,
+ ),
+ if (fromAccount != null &&
+ fromAccount!.date.compareTo(date) > 0 &&
+ !(date.compareTo(DateTime.now()) > 0))
+ InlineInfoCard(
+ margin: const EdgeInsets.fromLTRB(12, 8, 12, 16),
+ text: t.transaction.form.validators.date_after_account_creation,
+ mode: InlineInfoCardMode.warn,
),
- ),
- const SizedBox(width: 4),
- TextButton(
- style: buttonStyle,
- onPressed: () {
- showIntervalSelectoHelpDialog(context,
- selectedRecurrentRule: recurrentRule,
- onRecurrentRuleSelected: (res) {
- setState(() {
- recurrentRule = res;
- });
- });
- },
- child:
- Icon(recurrentRule.isRecurrent ? Icons.repeat : Icons.repeat_one),
- )
],
);
}
- TextButton buildTitleButton(BuildContext context) {
- return TextButton(
- onPressed: () => showTransactionTitleModal(
- context,
- initialTitle: title,
- ).then((modalRes) {
- if (modalRes == null) return;
+ Widget buildAccoutAndCategorySelectorRow(BuildContext context) {
+ final borderRadius = Radius.circular(_mainContainerRadius);
- setState(() {
- title = modalRes;
- });
- }),
- style: TextButton.styleFrom(
- backgroundColor: Colors.transparent,
- surfaceTintColor: Colors.transparent,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(4),
- ),
- padding: const EdgeInsets.symmetric(
- vertical: 16,
- horizontal: 8,
+ return DecoratedBox(
+ decoration: BoxDecoration(
+ color: transactionType.color(context).withOpacity(0.35),
+ borderRadius: BorderRadius.only(
+ bottomLeft: borderRadius,
+ bottomRight: borderRadius,
+ ),
),
- alignment: Alignment.centerLeft,
- ),
- child: Text(
- title.notEmptyString ?? t.transaction.form.title,
- softWrap: false,
- overflow: TextOverflow.fade,
- style: Theme.of(context).textTheme.titleMedium!.copyWith(
- fontWeight:
- title.isNotNullNorEmpty ? FontWeight.w400 : FontWeight.w300,
- ),
- ),
- );
+ child: SizedBox(
+ height: 74,
+ child: Row(
+ children: [
+ ...[
+ Expanded(
+ flex: 1,
+ child: selector(
+ title: t.general.account,
+ inputValue: fromAccount?.name,
+ borderRadius: BorderRadius.only(bottomLeft: borderRadius),
+ icon: fromAccount?.displayIcon(context) ??
+ IconDisplayer(
+ displayMode: IconDisplayMode.polygon,
+ icon: Icons.question_mark_rounded,
+ mainColor: Theme.of(context).colorScheme.primary,
+ ),
+ onClick: () async {
+ final modalRes =
+ await showAccountSelector(fromAccount!);
+
+ if (modalRes != null && modalRes.isNotEmpty) {
+ setState(() {
+ fromAccount = modalRes.first;
+ });
+ }
+ }),
+ ),
+ VerticalDivider(
+ color: transactionType.color(context).withOpacity(0.85),
+ thickness: 2,
+ )
+ ],
+ if (transactionType.isTransfer)
+ Expanded(
+ flex: 1,
+ child: ShakeWidget(
+ duration: const Duration(milliseconds: 200),
+ shakeCount: 1,
+ shakeOffset: 10,
+ key: _shakeKey,
+ child: selector(
+ title: t.transfer.form.to,
+ inputValue: transferAccount?.name,
+ borderRadius:
+ BorderRadius.only(bottomRight: borderRadius),
+ icon: transferAccount?.displayIcon(context) ??
+ IconDisplayer(
+ displayMode: IconDisplayMode.polygon,
+ icon: Icons.question_mark_rounded,
+ mainColor: Theme.of(context).colorScheme.primary,
+ ),
+ onClick: () async {
+ final modalRes =
+ await showAccountSelector(transferAccount);
+
+ if (modalRes != null && modalRes.isNotEmpty) {
+ setState(() {
+ transferAccount = modalRes.first;
+ });
+ }
+ }),
+ ),
+ ),
+ if (!transactionType.isTransfer)
+ Expanded(
+ flex: 1,
+ child: ShakeWidget(
+ duration: const Duration(milliseconds: 200),
+ shakeCount: 1,
+ shakeOffset: 10,
+ key: _shakeKey,
+ child: selector(
+ title: t.general.category,
+ inputValue: selectedCategory?.name,
+ borderRadius:
+ BorderRadius.only(bottomRight: borderRadius),
+ icon: IconDisplayer.fromCategory(
+ context,
+ category: selectedCategory ??
+ Category.fromDB(Category.unkown(), null),
+ size: 24,
+ ),
+ onClick: () => selectCategory(),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ));
}
}
diff --git a/lib/app/transactions/form/widgets/transaction_form_calculator.dart b/lib/app/transactions/form/widgets/transaction_form_calculator.dart
deleted file mode 100644
index e95dc379..00000000
--- a/lib/app/transactions/form/widgets/transaction_form_calculator.dart
+++ /dev/null
@@ -1,308 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:monekin/core/extensions/color.extensions.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
-
-/// String that identifies the remove character button
-const _removeButtonID = '⌫';
-
-class TransactionFormCalculator extends StatefulWidget {
- const TransactionFormCalculator(
- {super.key,
- required this.amountToConvert,
- this.onChange,
- this.onSubmit,
- this.showNegativeToggleButton = true});
-
- final double amountToConvert;
-
- final bool showNegativeToggleButton;
-
- /// If the calculator will start with a negative value
-//final bool initiallyNegative;
-
- final void Function(double amount)? onChange;
- final void Function()? onSubmit;
-
- @override
- State createState() =>
- _TransactionFormCalculatorState();
-}
-
-class _TransactionFormCalculatorState extends State {
- late String selectedAmount;
- double get valueToNumber => double.tryParse(selectedAmount) ?? 0;
-
- late bool isNegative;
-
- final FocusNode _focusNode = FocusNode();
- late FocusAttachment _focusAttachment;
-
- @override
- void initState() {
- super.initState();
-
- if (widget.amountToConvert == 0) {
- selectedAmount = '0';
- } else {
- selectedAmount =
- removeTrailingZeroes(widget.amountToConvert.toStringAsFixed(2));
- }
-
- isNegative = widget.amountToConvert.isNegative;
-
- _focusAttachment = _focusNode.attach(context, onKeyEvent: (node, event) {
- bool keyIsPressed = event.runtimeType == KeyDownEvent ||
- event.runtimeType == KeyRepeatEvent;
-
- if (!keyIsPressed) {
- return KeyEventResult.handled;
- }
-
- if ((event.logicalKey == LogicalKeyboardKey.browserBack ||
- event.logicalKey == LogicalKeyboardKey.goBack ||
- event.logicalKey == LogicalKeyboardKey.escape)) {
- Navigator.pop(context);
- }
-
- for (final (index, element) in [
- LogicalKeyboardKey.digit0,
- LogicalKeyboardKey.digit1,
- LogicalKeyboardKey.digit2,
- LogicalKeyboardKey.digit3,
- LogicalKeyboardKey.digit4,
- LogicalKeyboardKey.digit5,
- LogicalKeyboardKey.digit6,
- LogicalKeyboardKey.digit7,
- LogicalKeyboardKey.digit8,
- LogicalKeyboardKey.digit9,
- ].indexed) {
- if (event.logicalKey == element) {
- addToAmount(index.toStringAsFixed(0));
- break;
- }
- }
-
- for (final (index, element) in [
- LogicalKeyboardKey.numpad0,
- LogicalKeyboardKey.numpad1,
- LogicalKeyboardKey.numpad2,
- LogicalKeyboardKey.numpad3,
- LogicalKeyboardKey.numpad4,
- LogicalKeyboardKey.numpad5,
- LogicalKeyboardKey.numpad6,
- LogicalKeyboardKey.numpad7,
- LogicalKeyboardKey.numpad8,
- LogicalKeyboardKey.numpad9,
- ].indexed) {
- if (event.logicalKey == element) {
- addToAmount(index.toStringAsFixed(0));
- break;
- }
- }
-
- if (event.logicalKey == LogicalKeyboardKey.period ||
- event.logicalKey == LogicalKeyboardKey.numpadDecimal ||
- event.logicalKey == LogicalKeyboardKey.comma) {
- addToAmount('.');
- } else if (event.logicalKey == LogicalKeyboardKey.backspace ||
- event.logicalKey == LogicalKeyboardKey.delete) {
- onButtonPress(_removeButtonID);
- } else if (event.logicalKey == LogicalKeyboardKey.enter &&
- widget.onSubmit != null) {
- widget.onSubmit!();
- }
-
- return KeyEventResult.handled;
- });
-
- _focusNode.requestFocus();
- }
-
- @override
- void didUpdateWidget(oldWidget) {
- super.didUpdateWidget(oldWidget);
-
- if (oldWidget.amountToConvert != widget.amountToConvert ||
- oldWidget.amountToConvert.isNegative !=
- widget.amountToConvert.isNegative) {
- setState(() {
- isNegative = widget.amountToConvert.isNegative;
- });
- }
- }
-
- @override
- void dispose() {
- _focusNode.dispose();
- super.dispose();
- }
-
- String removeTrailingZeroes(String input) {
- if (!input.contains('.')) {
- return input;
- }
- int index = input.length - 1;
- while (input[index] == '0') {
- index--;
- }
- if (input[index] == '.') {
- index--;
- }
- return input.substring(0, index + 1);
- }
-
- void addToAmount(String text) {
- final decimalPlaces = selectedAmount.split('.').elementAtOrNull(1);
-
- if (decimalPlaces != null && decimalPlaces.length >= 2) {
- return;
- }
-
- updateSelectedAmount(selectedAmount + text);
- }
-
- void updateSelectedAmount(String newAmount) {
- setState(() {
- selectedAmount =
- "${isNegative ? '-' : ''}${newAmount.replaceAll('-', '')}";
-
- if (widget.onChange != null) {
- widget.onChange!(valueToNumber);
- }
- });
- }
-
- void onButtonPress(String text) {
- HapticFeedback.lightImpact();
-
- if (text == 'DONE') {
- if (widget.onSubmit != null) {
- widget.onSubmit!();
- }
-
- return;
- }
-
- if (text == 'AC') {
- updateSelectedAmount('0');
- } else if (text == _removeButtonID) {
- if (valueToNumber == 0) return;
-
- updateSelectedAmount(
- selectedAmount.substring(0, selectedAmount.length - 1));
- } else if (text == '-') {
- isNegative = !isNegative;
- updateSelectedAmount(selectedAmount);
- } else {
- addToAmount(text);
- }
- }
-
- Widget buildCalculatorButton(
- BuildContext context, {
- required String text,
- int flex = 1,
- Color? bgColor,
- Color? textColor,
- }) {
- textColor ??= AppColors.of(context).onSurface;
- bgColor ??= Colors.transparent;
-
- return Expanded(
- flex: flex,
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 0),
- child: ElevatedButton(
- style: ElevatedButton.styleFrom(
- backgroundColor: bgColor,
- shadowColor: bgColor.darken(0.15),
- surfaceTintColor: bgColor.darken(0.15),
- foregroundColor: textColor,
- disabledForegroundColor: textColor.withOpacity(0.3),
- disabledBackgroundColor: bgColor.withOpacity(0.3),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(0),
- ),
- elevation: 0,
- ),
- onPressed: text == 'DONE' && (valueToNumber == 0)
- ? null
- : () => onButtonPress(text),
- child: text == _removeButtonID || text == 'DONE'
- ? Icon(text == _removeButtonID
- ? Icons.backspace_rounded
- : Icons.check_rounded)
- : text == '-'
- ? const Icon(Icons.exposure_rounded)
- : Text(
- text,
- softWrap: false,
- style: const TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- ),
- );
- }
-
- @override
- Widget build(BuildContext context) {
- _focusAttachment.reparent();
-
- return Row(
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- buildCalculatorButton(context, text: '1'),
- buildCalculatorButton(context, text: '4'),
- buildCalculatorButton(context, text: '7'),
- buildCalculatorButton(context, text: '.'),
- ],
- ),
- ),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- buildCalculatorButton(context, text: '2'),
- buildCalculatorButton(context, text: '5'),
- buildCalculatorButton(context, text: '8'),
- buildCalculatorButton(context, text: '0'),
- ],
- ),
- ),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- buildCalculatorButton(context, text: '3'),
- buildCalculatorButton(context, text: '6'),
- buildCalculatorButton(context, text: '9'),
- buildCalculatorButton(context, text: _removeButtonID),
- ],
- ),
- ),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- buildCalculatorButton(context, text: 'AC'),
- if (widget.showNegativeToggleButton)
- buildCalculatorButton(context, text: '-'),
- buildCalculatorButton(context,
- bgColor: AppColors.of(context).primary,
- text: 'DONE',
- textColor: AppColors.of(context).onPrimary,
- flex: widget.showNegativeToggleButton ? 2 : 3),
- ],
- ),
- ),
- ],
- );
- }
-}
diff --git a/lib/app/transactions/label_value_info_list.dart b/lib/app/transactions/label_value_info_list.dart
index 78688053..070f5365 100644
--- a/lib/app/transactions/label_value_info_list.dart
+++ b/lib/app/transactions/label_value_info_list.dart
@@ -1,7 +1,5 @@
-import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:monekin/app/transactions/label_value_info_table.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
class LabelValueInfoListItem extends LabelValueInfoItem {
final Widget? trailing;
@@ -20,19 +18,28 @@ class LabelValueInfoList extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Column(
- children: items.mapIndexed((index, element) {
+ return ListView.separated(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: items.length,
+ separatorBuilder: (context, index) {
+ return const Divider(
+ // color: Colors.grey, // Customize the color of the separator
+ thickness: 1, // Customize the thickness of the separator
+ indent: 16,
+ endIndent: 16,
+ );
+ },
+ itemBuilder: (context, index) {
+ final element = items[index];
return ListTile(
minVerticalPadding: 0,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
title: Text(element.label),
subtitle: element.value,
trailing: element.trailing,
- tileColor: index % 2 != 0
- ? AppColors.of(context).surface
- : Theme.of(context).colorScheme.surfaceContainerLowest,
);
- }).toList(),
+ },
);
}
}
diff --git a/lib/app/transactions/label_value_info_table.dart b/lib/app/transactions/label_value_info_table.dart
index 7c69be41..4ef2427e 100644
--- a/lib/app/transactions/label_value_info_table.dart
+++ b/lib/app/transactions/label_value_info_table.dart
@@ -1,6 +1,5 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
class LabelValueInfoItem {
final Widget value;
@@ -19,45 +18,46 @@ class LabelValueInfoTable extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Table(
- border: TableBorder(borderRadius: BorderRadius.circular(0)),
- defaultVerticalAlignment: TableCellVerticalAlignment.middle,
- columnWidths: const {
- 0: FlexColumnWidth(3),
- 1: FlexColumnWidth(7),
- },
- children: items
- .mapIndexed(
- (i, e) => TableRow(
- decoration: BoxDecoration(
- color: i % 2 != 0
- ? AppColors.of(context).surface
- : Theme.of(context).colorScheme.surfaceContainerLowest,
- ),
- children: [
- TableCell(
- child: Padding(
- padding: const EdgeInsets.symmetric(
- vertical: 12,
- horizontal: 16,
- ),
- child: Text(
- e.label,
- style: const TextStyle(fontWeight: FontWeight.w300),
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ child: Table(
+ border: TableBorder(
+ borderRadius: BorderRadius.circular(0),
+ horizontalInside:
+ BorderSide(width: 1, color: Theme.of(context).dividerColor)),
+ defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+ columnWidths: const {
+ 0: FlexColumnWidth(3),
+ 1: FlexColumnWidth(7),
+ },
+ children: items
+ .mapIndexed(
+ (i, e) => TableRow(
+ children: [
+ TableCell(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 12,
+ horizontal: 4,
+ ),
+ child: Text(
+ e.label,
+ style: const TextStyle(fontWeight: FontWeight.w300),
+ ),
),
),
- ),
- TableCell(
- child: Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
- child: e.value,
+ TableCell(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 2, vertical: 4),
+ child: e.value,
+ ),
),
- ),
- ],
- ),
- )
- .toList(),
+ ],
+ ),
+ )
+ .toList(),
+ ),
);
}
}
diff --git a/lib/app/transactions/transaction_details.page.dart b/lib/app/transactions/transaction_details.page.dart
index b4eca038..33e30c1d 100644
--- a/lib/app/transactions/transaction_details.page.dart
+++ b/lib/app/transactions/transaction_details.page.dart
@@ -259,7 +259,7 @@ class _TransactionDetailsPageState extends State {
subtitleTextStyle: Theme.of(context).textTheme.labelSmall!.copyWith(
color: isNext
? transaction.nextPayStatus!.color(context).darken(0.6)
- : AppColors.of(context).primaryContainer,
+ : Theme.of(context).colorScheme.primaryContainer,
),
leading: Icon(
isNext ? transaction.nextPayStatus!.icon : Icons.access_time,
@@ -274,7 +274,7 @@ class _TransactionDetailsPageState extends State {
transaction.nextPayStatus!
.displayDaysToPay(context, transaction.daysToPay()),
style: TextStyle(
- color: AppColors.of(context).onSurface,
+ color: Theme.of(context).colorScheme.onSurface,
),
),
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
@@ -419,8 +419,8 @@ class _TransactionDetailsPageState extends State {
final color = showRecurrencyStatus
? isDarkTheme
- ? AppColors.of(context).primary
- : AppColors.of(context).primary.lighten(0.2)
+ ? Theme.of(context).colorScheme.primary
+ : Theme.of(context).colorScheme.primary.lighten(0.2)
: transaction.status!.color;
return translucentCard(
@@ -581,7 +581,9 @@ class _TransactionDetailsPageState extends State {
value: buildInfoTileWithIconAndColor(
icon: transaction
.receivingAccount!.icon,
- color: AppColors.of(context).primary,
+ color: Theme.of(context)
+ .colorScheme
+ .primary,
data: transaction
.receivingAccount!.name,
),
@@ -790,7 +792,7 @@ class _TransactionDetailsPageState extends State {
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
- color: AppColors.of(context).onSurface.withOpacity(0.85)),
+ color: Theme.of(context).colorScheme.onSurface.withOpacity(0.85)),
),
);
}
@@ -833,7 +835,7 @@ class _TransactionDetailHeader extends SliverPersistentHeaderDelegate {
final shrinkPercent = shrinkOffset / maxExtent;
return Container(
- color: AppColors.of(context).surface,
+ color: Theme.of(context).colorScheme.surface,
padding: const EdgeInsets.only(left: 24, right: 24, top: 16, bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
diff --git a/lib/app/transactions/transactions.page.dart b/lib/app/transactions/transactions.page.dart
index 01f10a75..087bff26 100644
--- a/lib/app/transactions/transactions.page.dart
+++ b/lib/app/transactions/transactions.page.dart
@@ -3,13 +3,12 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:monekin/app/home/widgets/new_transaction_fl_button.dart';
import 'package:monekin/app/layout/tabs.dart';
-import 'package:monekin/app/transactions/form/transaction_form.page.dart';
import 'package:monekin/app/transactions/widgets/bulk_edit_transaction_modal.dart';
import 'package:monekin/app/transactions/widgets/transaction_list.dart';
import 'package:monekin/core/database/services/transaction/transaction_service.dart';
import 'package:monekin/core/models/transaction/transaction.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
import 'package:monekin/core/presentation/widgets/confirm_dialog.dart';
import 'package:monekin/core/presentation/widgets/filter_row_indicator.dart';
import 'package:monekin/core/presentation/widgets/monekin_popup_menu_button.dart';
@@ -18,7 +17,6 @@ import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_
import 'package:monekin/core/presentation/widgets/skeleton.dart';
import 'package:monekin/core/presentation/widgets/transaction_filter/filter_sheet_modal.dart';
import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart';
-import 'package:monekin/core/routes/route_utils.dart';
import 'package:monekin/core/utils/list_tile_action_item.dart';
import 'package:monekin/i18n/translations.g.dart';
@@ -145,14 +143,7 @@ class _TransactionsPageState extends State {
icon: const Icon(Icons.filter_alt_outlined)),
],
),
- floatingActionButton: FloatingActionButton.extended(
- icon: const Icon(Icons.add_rounded),
- label: Text(t.transaction.create),
- onPressed: () => RouteUtils.pushRoute(
- context,
- const TransactionFormPage(),
- ),
- ),
+ floatingActionButton: const NewTransactionButton(isExtended: true),
body: Column(
children: [
if (filters.hasFilter) ...[
@@ -177,7 +168,7 @@ class _TransactionsPageState extends State {
return Card(
elevation: 2,
- //color: AppColors.of(context).primary,
+ //color: Theme.of(context).colorScheme.primary,
margin: const EdgeInsets.all(8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
@@ -277,8 +268,8 @@ class _TransactionsPageState extends State {
AppBar selectedTransactionsAppbar() {
return AppBar(
- backgroundColor: AppColors.of(context).primary,
- foregroundColor: AppColors.of(context).onPrimary,
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ foregroundColor: Theme.of(context).colorScheme.onPrimary,
leading: IconButton(
onPressed: () {
cleanSelectedTransactions();
diff --git a/lib/app/transactions/widgets/transaction_list_tile.dart b/lib/app/transactions/widgets/transaction_list_tile.dart
index 9a4304ec..3a0c6c61 100644
--- a/lib/app/transactions/widgets/transaction_list_tile.dart
+++ b/lib/app/transactions/widgets/transaction_list_tile.dart
@@ -94,7 +94,7 @@ class TransactionListTile extends StatelessWidget {
Icon(
transaction.status?.icon ?? Icons.repeat,
color: transaction.status?.color.darken(0.1) ??
- AppColors.of(context).primary,
+ Theme.of(context).colorScheme.primary,
size: 12,
)
],
@@ -240,14 +240,15 @@ class TransactionListTile extends StatelessWidget {
Icon(
Icons.check,
size: 24,
- color: AppColors.of(context).surface,
+ color: Theme.of(context).colorScheme.surface,
),
],
)
: transaction.getDisplayIcon(context, size: 28, padding: 6),
),
selected: isSelected,
- selectedTileColor: AppColors.of(context).primary.withOpacity(0.15),
+ selectedTileColor:
+ Theme.of(context).colorScheme.primary.withOpacity(0.15),
onTap: onTap ??
() {
RouteUtils.pushRoute(
diff --git a/lib/core/database/services/account/account_service.dart b/lib/core/database/services/account/account_service.dart
index f4768f4e..64bd6db5 100644
--- a/lib/core/database/services/account/account_service.dart
+++ b/lib/core/database/services/account/account_service.dart
@@ -127,16 +127,25 @@ class AccountService {
final initialBalanceQuery = db
.customSelect(
"""
- SELECT COALESCE(SUM(accounts.iniValue ${convertToPreferredCurrency ? ' * COALESCE(excRate.exchangeRate, 1)' : ''} ), 0) AS balance
- FROM accounts
- ${convertToPreferredCurrency ? _joinAccountAndRate(date) : ''}
- ${accountIds != null ? 'WHERE accounts.id IN (${List.filled(accountIds.length, '?').join(', ')})' : ''}
- """,
+ SELECT COALESCE(
+ SUM(
+ CASE WHEN accounts.date > ? THEN 0
+ ELSE accounts.iniValue
+ ${convertToPreferredCurrency ? ' * COALESCE(excRate.exchangeRate, 1)' : ''}
+ END
+ )
+ , 0)
+ AS balance
+ FROM accounts
+ ${convertToPreferredCurrency ? _joinAccountAndRate(date) : ''}
+ ${accountIds != null ? 'WHERE accounts.id IN (${List.filled(accountIds.length, '?').join(', ')})' : ''}
+ """,
readsFrom: {
db.accounts,
if (convertToPreferredCurrency) db.exchangeRates
},
variables: [
+ Variable.withDateTime(date),
if (convertToPreferredCurrency) Variable.withDateTime(date),
if (accountIds != null)
for (final id in accountIds) Variable.withString(id)
diff --git a/lib/core/extensions/bool.extension.dart b/lib/core/extensions/bool.extension.dart
new file mode 100644
index 00000000..61f41828
--- /dev/null
+++ b/lib/core/extensions/bool.extension.dart
@@ -0,0 +1,21 @@
+extension BoolConversions on bool {
+ /// Convert to integer (1 for true, 0 for false)
+ int toInt() {
+ return this ? 1 : 0;
+ }
+
+ /// Convert to double (1.0 for true, 0.0 for false)
+ double toDouble() {
+ return this ? 1.0 : 0.0;
+ }
+
+ /// Chainable 'and' operation
+ bool and(bool other) {
+ return this && other;
+ }
+
+ /// Chainable 'or' operation
+ bool or(bool other) {
+ return this || other;
+ }
+}
diff --git a/lib/core/extensions/color.extensions.dart b/lib/core/extensions/color.extensions.dart
index 0aaf3454..3c3194e0 100644
--- a/lib/core/extensions/color.extensions.dart
+++ b/lib/core/extensions/color.extensions.dart
@@ -32,7 +32,7 @@ extension ColorBrightness on Color {
assert(amount >= -1 && amount <= 1);
if (amount < 0) {
- return lighten(amount.abs());
+ return darken(amount.abs());
}
return Color.fromARGB(
diff --git a/lib/core/extensions/lists.extensions.dart b/lib/core/extensions/lists.extensions.dart
index 08307d47..7739b673 100644
--- a/lib/core/extensions/lists.extensions.dart
+++ b/lib/core/extensions/lists.extensions.dart
@@ -44,3 +44,15 @@ extension PrintListItem on Iterable {
return join(', ');
}
}
+
+extension ListEqualityCheck on List {
+ bool allItemsEqual() {
+ if (isEmpty) {
+ return true; // Return true for an empty list as a convention
+ }
+
+ // Compare all elements to the first one
+ final firstItem = this[0];
+ return every((item) => item == firstItem);
+ }
+}
diff --git a/lib/core/extensions/numbers.extensions.dart b/lib/core/extensions/numbers.extensions.dart
index af97b45e..f3a71836 100644
--- a/lib/core/extensions/numbers.extensions.dart
+++ b/lib/core/extensions/numbers.extensions.dart
@@ -12,4 +12,13 @@ extension FileFormatter on num {
return '${NumberFormat.decimalPatternDigits(decimalDigits: 1).format(this / pow(base, digitGroups))} ${units[digitGroups]}';
}
+
+ double roundWithDecimals(int decimalPlaces) {
+ if (isInfinite || isNaN) {
+ return toDouble();
+ }
+
+ num mod = pow(10.0, decimalPlaces);
+ return ((this * mod).round().toDouble() / mod);
+ }
}
diff --git a/lib/core/models/account/account.dart b/lib/core/models/account/account.dart
index 325df96e..0f78bce6 100644
--- a/lib/core/models/account/account.dart
+++ b/lib/core/models/account/account.dart
@@ -79,8 +79,8 @@ class Account extends AccountInDB {
return color != null
? ColorHex.get(color!)
: Theme.of(context).brightness == Brightness.dark
- ? AppColors.of(context).primaryContainer
- : AppColors.of(context).primary;
+ ? Theme.of(context).colorScheme.primaryContainer
+ : Theme.of(context).colorScheme.primary;
}
IconDisplayer displayIcon(
diff --git a/lib/core/models/transaction/next_pay_status.enum.dart b/lib/core/models/transaction/next_pay_status.enum.dart
index 93993bbc..9c15cea7 100644
--- a/lib/core/models/transaction/next_pay_status.enum.dart
+++ b/lib/core/models/transaction/next_pay_status.enum.dart
@@ -14,7 +14,7 @@ enum NextPayStatus {
Color color(BuildContext context) {
if (this == planified) {
- return AppColors.of(context).primary;
+ return Theme.of(context).colorScheme.primary;
} else if (this == delayed) {
return AppColors.of(context).danger;
}
diff --git a/lib/core/presentation/animations/animated_expanded.dart b/lib/core/presentation/animations/animated_expanded.dart
index c98ac626..9a8ecfdb 100644
--- a/lib/core/presentation/animations/animated_expanded.dart
+++ b/lib/core/presentation/animations/animated_expanded.dart
@@ -1,8 +1,23 @@
import 'package:flutter/material.dart';
+/// A widget that smoothly expands or collapses its child with an animation.
+///
+/// The animation can be configured to expand either vertically or horizontally
+/// and includes both size and fade transitions.
+///
+/// [AnimatedExpanded] is useful for cases where you want to dynamically show
+/// or hide content with a smooth animation, such as expanding a section of a
+/// list or a collapsible panel.
+///
+/// The widget automatically listens for changes to the [expand] property and
+/// triggers the animation accordingly.
class AnimatedExpanded extends StatefulWidget {
+ /// The widget to display inside the animated container.
final Widget child;
+
+ /// A boolean flag indicating whether to expand or collapse the [child]
final bool expand;
+
final Duration duration;
final Curve sizeCurve;
final Axis axis;
@@ -85,39 +100,42 @@ class _AnimatedExpandedState extends State
}
}
-// Animated Switcher may be needed, if old data gets wiped immediately
-// we want to keep the old UI when a transition occurs to make it smoother
+/// A widget that switches between two children with an animated size transition.
+///
+/// [AnimatedSizeSwitcher] ensures that the old widget remains visible until the new one
+/// has fully transitioned in, making the transition smoother. It uses [AnimatedSwitcher]
+/// internally, with a custom transition that animates the size of the child widget.
class AnimatedSizeSwitcher extends StatelessWidget {
const AnimatedSizeSwitcher({
required this.child,
- this.sizeCurve = Curves.easeInOutCubicEmphasized,
- this.sizeDuration = const Duration(milliseconds: 800),
- this.switcherDuration = const Duration(milliseconds: 250),
- this.sizeAlignment = AlignmentDirectional.center,
- this.clipBehavior = Clip.hardEdge,
+ this.duration = const Duration(milliseconds: 250),
this.enabled = true,
+ this.axis = Axis.vertical,
super.key,
});
+
final Widget child;
- final Curve sizeCurve;
- final Duration sizeDuration;
- final Duration switcherDuration;
- final AlignmentDirectional sizeAlignment;
- final Clip clipBehavior;
+ final Duration duration;
final bool enabled;
+ final Axis axis;
@override
Widget build(BuildContext context) {
if (enabled == false) return child;
- return AnimatedSize(
- clipBehavior: clipBehavior,
- duration: sizeDuration,
- curve: sizeCurve,
- alignment: sizeAlignment,
- child: AnimatedSwitcher(
- duration: switcherDuration,
- child: child,
- ),
+
+ return AnimatedSwitcher(
+ switchInCurve: Curves.fastEaseInToSlowEaseOut,
+ switchOutCurve: Curves.fastOutSlowIn,
+ duration: duration,
+ transitionBuilder: (child, animation) {
+ return SizeTransition(
+ axisAlignment: 1,
+ sizeFactor: animation,
+ axis: axis,
+ child: child,
+ );
+ },
+ child: child,
);
}
}
diff --git a/lib/core/presentation/animations/shake/fade_in.dart b/lib/core/presentation/animations/fade_in.dart
similarity index 100%
rename from lib/core/presentation/animations/shake/fade_in.dart
rename to lib/core/presentation/animations/fade_in.dart
diff --git a/lib/core/presentation/animations/scaled_animated_switcher.dart b/lib/core/presentation/animations/scaled_animated_switcher.dart
new file mode 100644
index 00000000..8af310a3
--- /dev/null
+++ b/lib/core/presentation/animations/scaled_animated_switcher.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+
+class ScaledAnimatedSwitcher extends StatelessWidget {
+ const ScaledAnimatedSwitcher({
+ required this.keyToWatch,
+ required this.child,
+ this.duration = const Duration(milliseconds: 450),
+ super.key,
+ });
+
+ final String keyToWatch;
+ final Widget child;
+ final Duration duration;
+
+ @override
+ Widget build(BuildContext context) {
+ return AnimatedSwitcher(
+ duration: duration,
+ switchInCurve: Curves.easeInOutCubic,
+ switchOutCurve: Curves.easeOut,
+ transitionBuilder: (Widget child, Animation animation) {
+ final fadeAnimation = Tween(begin: 0.0, end: 1.0).animate(
+ CurvedAnimation(
+ parent: animation,
+ curve: const Interval(0.5, 1),
+ ),
+ );
+
+ final scaleAnimation = Tween(begin: 0, end: 1.0).animate(
+ CurvedAnimation(
+ parent: animation,
+ curve: const Interval(0, 1.0),
+ ),
+ );
+
+ return FadeTransition(
+ opacity: fadeAnimation,
+ child: ScaleTransition(
+ alignment: Alignment.center,
+ scale: scaleAnimation,
+ child: child,
+ ),
+ );
+ },
+ child: SizedBox(key: ValueKey(keyToWatch), child: child),
+ );
+ }
+}
diff --git a/lib/core/presentation/animations/shake/shake_widget.dart b/lib/core/presentation/animations/shake_widget.dart
similarity index 100%
rename from lib/core/presentation/animations/shake/shake_widget.dart
rename to lib/core/presentation/animations/shake_widget.dart
diff --git a/lib/core/presentation/app_colors.dart b/lib/core/presentation/app_colors.dart
index 581b8838..74bb96a5 100644
--- a/lib/core/presentation/app_colors.dart
+++ b/lib/core/presentation/app_colors.dart
@@ -7,39 +7,21 @@ class AppColors extends ThemeExtension {
const AppColors({
required this.danger,
required this.success,
+ required this.brand,
required this.light,
required this.dark,
required this.shadowColor,
required this.shadowColorLight,
- required this.brand,
- required this.inputFill,
- required this.primary,
- required this.onPrimary,
- required this.primaryContainer,
- required this.onPrimaryContainer,
- required this.surface,
- required this.onSurface,
- required this.modalBackground,
});
final Color danger;
final Color success;
final Color brand;
- final Color inputFill;
final Color light;
final Color dark;
final Color shadowColor;
final Color shadowColorLight;
- /* ---- From the material color scheme: ---- */
- final Color primary;
- final Color onPrimary;
- final Color primaryContainer;
- final Color onPrimaryContainer;
- final Color surface;
- final Color onSurface;
- final Color modalBackground;
-
static AppColors fromColorScheme(ColorScheme colorScheme) {
final isDark = colorScheme.brightness == Brightness.dark;
@@ -48,30 +30,14 @@ class AppColors extends ThemeExtension {
success:
isDark ? Colors.lightGreen : const Color.fromARGB(255, 55, 161, 59),
brand: isDark ? const Color.fromARGB(255, 128, 134, 177) : brandBlue,
-
light: colorScheme.surfaceContainerLow,
-
dark: colorScheme.inverseSurface,
-
shadowColor: isDark
? const Color.fromARGB(105, 189, 189, 189)
: const Color.fromARGB(100, 90, 90, 90),
-
shadowColorLight: isDark
? const Color.fromARGB(40, 116, 116, 116)
: const Color.fromARGB(44, 90, 90, 90),
-
- inputFill: colorScheme.surfaceContainerHighest,
-
- // Colors from the material color scheme:
- primary: colorScheme.primary,
- onPrimary: colorScheme.onPrimary,
- primaryContainer: colorScheme.primaryContainer,
- onPrimaryContainer: colorScheme.onPrimaryContainer,
- surface: colorScheme.surface,
- onSurface: colorScheme.onSurface,
-
- modalBackground: colorScheme.surfaceContainer,
);
}
@@ -84,35 +50,21 @@ class AppColors extends ThemeExtension {
Color? danger,
Color? success,
Color? brand,
- Color? primary,
Color? inputFill,
Color? dark,
Color? light,
Color? shadowColor,
Color? shadowColorLight,
- Color? onPrimary,
- Color? primaryContainer,
- Color? onPrimaryContainer,
- Color? surface,
- Color? onSurface,
Color? modalBackground,
}) {
return AppColors(
danger: danger ?? this.danger,
success: success ?? this.success,
- inputFill: inputFill ?? this.inputFill,
light: light ?? this.light,
dark: dark ?? this.dark,
brand: brand ?? this.brand,
shadowColor: shadowColor ?? this.shadowColor,
shadowColorLight: shadowColorLight ?? this.shadowColorLight,
- primary: primary ?? this.primary,
- onPrimary: onPrimary ?? this.onPrimary,
- primaryContainer: primaryContainer ?? this.primaryContainer,
- onPrimaryContainer: onPrimaryContainer ?? this.onPrimaryContainer,
- surface: surface ?? this.surface,
- onSurface: onSurface ?? this.onSurface,
- modalBackground: modalBackground ?? this.modalBackground,
);
}
@@ -124,7 +76,6 @@ class AppColors extends ThemeExtension {
return AppColors(
danger: Color.lerp(danger, other.danger, t) ?? danger,
success: Color.lerp(success, other.success, t) ?? success,
- inputFill: Color.lerp(inputFill, other.inputFill, t) ?? inputFill,
light: Color.lerp(light, other.light, t) ?? light,
dark: Color.lerp(dark, other.dark, t) ?? dark,
shadowColor: Color.lerp(shadowColor, other.shadowColor, t) ?? shadowColor,
@@ -132,18 +83,29 @@ class AppColors extends ThemeExtension {
Color.lerp(shadowColorLight, other.shadowColorLight, t) ??
shadowColorLight,
brand: Color.lerp(brand, other.brand, t) ?? brand,
- primary: Color.lerp(primary, other.primary, t) ?? primary,
- onPrimary: Color.lerp(onPrimary, other.onPrimary, t) ?? onPrimary,
- primaryContainer:
- Color.lerp(primaryContainer, other.primaryContainer, t) ??
- primaryContainer,
- onPrimaryContainer:
- Color.lerp(onPrimaryContainer, other.onPrimaryContainer, t) ??
- onPrimaryContainer,
- surface: Color.lerp(surface, other.surface, t) ?? surface,
- onSurface: Color.lerp(onSurface, other.onSurface, t) ?? onSurface,
- modalBackground: Color.lerp(modalBackground, other.modalBackground, t) ??
- modalBackground,
);
}
}
+
+extension CustomThemeDataExt on ThemeData {
+ CustomColorSchemeExtended get colorSchemeExtended {
+ return CustomColorSchemeExtended(colorScheme, brightness);
+ }
+}
+
+class CustomColorSchemeExtended {
+ final ColorScheme _colorScheme;
+ final Brightness _brightness;
+
+ CustomColorSchemeExtended(this._colorScheme, this._brightness);
+
+ Color get modalBackground => _colorScheme.surfaceContainer;
+ Color get inputFill => _colorScheme.surfaceContainerHighest;
+ Color get cardColor => _colorScheme.surfaceContainer;
+ Color get dashboardHeader => _brightness == Brightness.light
+ ? _colorScheme.primary
+ : _colorScheme.primaryContainer;
+ Color get onDashboardHeader => _brightness == Brightness.light
+ ? _colorScheme.onPrimary
+ : _colorScheme.onPrimaryContainer;
+}
diff --git a/lib/core/presentation/theme.dart b/lib/core/presentation/theme.dart
index d2c4a200..d9227b31 100644
--- a/lib/core/presentation/theme.dart
+++ b/lib/core/presentation/theme.dart
@@ -87,10 +87,11 @@ ThemeData getThemeData(
return theme.copyWith(
scaffoldBackgroundColor: theme.colorScheme.surface,
dividerTheme: const DividerThemeData(space: 0),
- cardColor: theme.colorScheme.surface,
+ cardColor: theme.colorSchemeExtended.cardColor,
+ cardTheme: CardTheme(color: theme.colorSchemeExtended.cardColor),
inputDecorationTheme: InputDecorationTheme(
filled: true,
- fillColor: theme.colorScheme.surfaceContainerHighest,
+ fillColor: theme.colorSchemeExtended.inputFill,
isDense: true,
floatingLabelStyle: TextStyle(
backgroundColor: theme.colorScheme.surface.withOpacity(0.5),
@@ -107,7 +108,7 @@ ThemeData getThemeData(
bottomSheetTheme: theme.bottomSheetTheme.copyWith(
elevation: 0,
dragHandleSize: const Size(25, 4),
- modalBackgroundColor: customAppColors.modalBackground,
+ modalBackgroundColor: theme.colorSchemeExtended.modalBackground,
dragHandleColor: Colors.grey[300],
clipBehavior: Clip.hardEdge,
),
diff --git a/lib/core/presentation/widgets/animated_progress_bar.dart b/lib/core/presentation/widgets/animated_progress_bar.dart
index 7763f53f..6046f02b 100644
--- a/lib/core/presentation/widgets/animated_progress_bar.dart
+++ b/lib/core/presentation/widgets/animated_progress_bar.dart
@@ -45,7 +45,7 @@ class _AnimatedProgressBarState extends State {
topRight: Radius.circular(widget.radius),
);
- final barColor = widget.color ?? AppColors.of(context).primary;
+ final barColor = widget.color ?? Theme.of(context).colorScheme.primary;
return TweenAnimationBuilder(
duration: Duration(milliseconds: widget.animationDuration),
diff --git a/lib/core/presentation/widgets/card_with_header.dart b/lib/core/presentation/widgets/card_with_header.dart
index 4fd15039..efa82724 100644
--- a/lib/core/presentation/widgets/card_with_header.dart
+++ b/lib/core/presentation/widgets/card_with_header.dart
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
-
-import '../app_colors.dart';
+import 'package:monekin/i18n/translations.g.dart';
/// The radius of the `CardWithHeader` widget, a very useful widget through the app
const cardWithHeaderRadius = 12.0;
@@ -9,82 +8,59 @@ class CardWithHeader extends StatelessWidget {
const CardWithHeader({
super.key,
required this.title,
+ this.subtitle,
required this.body,
- this.onHeaderButtonClick,
- this.headerButtonIcon = Icons.arrow_forward_ios_rounded,
this.bodyPadding = const EdgeInsets.all(0),
+ this.footer,
});
final Widget body;
+ final Widget? footer;
final String title;
-
- final IconData headerButtonIcon;
+ final String? subtitle;
final EdgeInsets bodyPadding;
- final void Function()? onHeaderButtonClick;
-
@override
Widget build(BuildContext context) {
- const double iconSize = 16;
-
- return Container(
+ return Card(
clipBehavior: Clip.hardEdge,
- decoration: BoxDecoration(
- color: AppColors.of(context).surface,
- borderRadius: BorderRadius.circular(cardWithHeaderRadius),
- boxShadow: [
- BoxShadow(
- color: AppColors.of(context).shadowColorLight,
- blurRadius: cardWithHeaderRadius,
- offset: const Offset(0, 0),
- spreadRadius: 4,
- ),
- ],
- ),
- foregroundDecoration: BoxDecoration(
- borderRadius: BorderRadius.circular(cardWithHeaderRadius),
- border: Border.all(
- width: 1,
- color: Theme.of(context).dividerColor,
- ),
- ),
margin: const EdgeInsets.all(0),
+ elevation: 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
clipBehavior: Clip.hardEdge,
- padding: EdgeInsets.fromLTRB(
- 16,
- onHeaderButtonClick != null ? 2 : iconSize - 6,
- 2,
- onHeaderButtonClick != null ? 2 : iconSize - 6),
- decoration: BoxDecoration(
- borderRadius: const BorderRadius.only(
+ padding: const EdgeInsets.fromLTRB(16, 12, 2, 4),
+ decoration: const BoxDecoration(
+ borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
- color: AppColors.of(context).light,
+ // color: AppColors.of(context).light,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Text(title,
- style: const TextStyle(
- fontSize: 16, fontWeight: FontWeight.w700)),
- if (onHeaderButtonClick != null)
- IconButton(
- onPressed: onHeaderButtonClick,
- iconSize: iconSize,
- color: AppColors.of(context).primary,
- icon: Icon(headerButtonIcon),
- )
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(title,
+ style: const TextStyle(
+ fontSize: 18, fontWeight: FontWeight.w600)),
+ if (subtitle != null)
+ Text(
+ subtitle!,
+ style: const TextStyle(
+ fontSize: 12, fontWeight: FontWeight.w300),
+ ),
+ ],
+ ),
],
),
),
- const Divider(),
Material(
type: MaterialType.transparency,
clipBehavior: Clip.antiAliasWithSaveLayer,
@@ -92,9 +68,43 @@ class CardWithHeader extends StatelessWidget {
padding: bodyPadding,
child: body,
),
- )
+ ),
+ if (footer != null) footer!
],
),
);
}
}
+
+class CardFooterWithSingleButton extends StatelessWidget {
+ const CardFooterWithSingleButton({super.key, this.text, this.onButtonClick});
+
+ final String? text;
+ final VoidCallback? onButtonClick;
+
+ @override
+ Widget build(BuildContext context) {
+ final t = Translations.of(context);
+
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Divider(
+ thickness: 2,
+ indent: 16,
+ endIndent: 16,
+ ),
+ Container(
+ alignment: Alignment.centerRight,
+ padding: const EdgeInsets.fromLTRB(2, 4, 2, 4),
+ child: TextButton.icon(
+ onPressed: onButtonClick,
+ iconAlignment: IconAlignment.end,
+ icon: const Icon(Icons.arrow_forward_ios_rounded, size: 14),
+ label: Text(text ?? t.general.see_more),
+ ),
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/core/presentation/widgets/count_indicator.dart b/lib/core/presentation/widgets/count_indicator.dart
index 8f38bb20..bfc0df09 100644
--- a/lib/core/presentation/widgets/count_indicator.dart
+++ b/lib/core/presentation/widgets/count_indicator.dart
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
-import 'package:monekin/core/presentation/app_colors.dart';
class CountIndicatorWithExpandArrow extends StatelessWidget {
const CountIndicatorWithExpandArrow({
@@ -46,13 +45,13 @@ class CountIndicator extends StatelessWidget {
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
- color: AppColors.of(context).primary,
+ color: Theme.of(context).colorScheme.primary,
),
child: Text(
countToDisplay.toString(),
style: Theme.of(context).textTheme.labelSmall!.copyWith(
fontWeight: fontWeight,
- color: AppColors.of(context).onPrimary,
+ color: Theme.of(context).colorScheme.onPrimary,
),
),
);
diff --git a/lib/core/presentation/widgets/currency_selector_modal.dart b/lib/core/presentation/widgets/currency_selector_modal.dart
index 81b3dea7..6a95a8cd 100644
--- a/lib/core/presentation/widgets/currency_selector_modal.dart
+++ b/lib/core/presentation/widgets/currency_selector_modal.dart
@@ -69,7 +69,7 @@ class _CurrencySelectorModalState extends State {
title: t.currencies.select_a_currency,
endWidget: Chip(
side: BorderSide(color: colors.primary, width: 2),
- // backgroundColor: AppColors.of(context).primaryLight,
+ // backgroundColor: Theme.of(context).colorScheme.primaryLight,
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -171,7 +171,7 @@ class _CurrencySelectorModalState extends State {
},
),
ScrollableWithBottomGradient.buildPositionedGradient(
- AppColors.of(context).modalBackground),
+ Theme.of(context).colorSchemeExtended.modalBackground),
]),
),
],
diff --git a/lib/core/presentation/widgets/dates/outlinedButtonStacked.dart b/lib/core/presentation/widgets/dates/outlinedButtonStacked.dart
index b006bfc8..559a47c3 100644
--- a/lib/core/presentation/widgets/dates/outlinedButtonStacked.dart
+++ b/lib/core/presentation/widgets/dates/outlinedButtonStacked.dart
@@ -42,7 +42,7 @@ class OutlinedButtonStacked extends StatelessWidget {
Widget build(BuildContext context) {
return Tappable(
onTap: onTap,
- borderRadius: borderRadius,
+ borderRadius: BorderRadius.circular(borderRadius),
bgColor: Colors.transparent,
child: _OutlinedContainer(
filled: filled,
diff --git a/lib/core/presentation/widgets/dates/segmented_calendar_button.dart b/lib/core/presentation/widgets/dates/segmented_calendar_button.dart
index ad7d8850..f1b5333d 100644
--- a/lib/core/presentation/widgets/dates/segmented_calendar_button.dart
+++ b/lib/core/presentation/widgets/dates/segmented_calendar_button.dart
@@ -58,10 +58,11 @@ class _SegmentedCalendarButtonState extends State {
onPressed: disabled ? null : onPressed,
icon: Icon(icon),
iconSize: widget.buttonHeight - padding * 2,
- disabledColor: AppColors.of(context).primary,
- color: AppColors.of(context).primary,
+ disabledColor: Theme.of(context).colorScheme.primary,
+ color: Theme.of(context).colorScheme.primary,
style: IconButton.styleFrom(
- side: BorderSide(color: AppColors.of(context).primary, width: 2),
+ side: BorderSide(
+ color: Theme.of(context).colorScheme.primary, width: 2),
fixedSize: Size.fromHeight(widget.buttonHeight),
padding: EdgeInsets.all(padding),
minimumSize: const Size.fromHeight(0),
diff --git a/lib/core/presentation/widgets/expansion_panel/single_expansion_panel.dart b/lib/core/presentation/widgets/expansion_panel/single_expansion_panel.dart
index ad5b3500..7ec3ad89 100644
--- a/lib/core/presentation/widgets/expansion_panel/single_expansion_panel.dart
+++ b/lib/core/presentation/widgets/expansion_panel/single_expansion_panel.dart
@@ -3,8 +3,11 @@ import 'package:monekin/core/presentation/widgets/expansion_panel/expansion_pane
import 'package:monekin/i18n/translations.g.dart';
class SingleExpansionPanel extends StatefulWidget {
- const SingleExpansionPanel(
- {super.key, required this.child, this.sidePadding = 0});
+ const SingleExpansionPanel({
+ super.key,
+ required this.child,
+ this.sidePadding = 0,
+ });
final Widget child;
final double sidePadding;
@@ -29,11 +32,14 @@ class _SingleExpansionPanelState extends State {
},
children: [
ExpansionPanel(
+ backgroundColor: Theme.of(context).colorScheme.surface,
// canTapOnHeader: true,
headerBuilder: (context, isExpanded) {
return Padding(
padding: EdgeInsets.symmetric(
- vertical: 0, horizontal: widget.sidePadding),
+ vertical: 0,
+ horizontal: widget.sidePadding,
+ ),
child: Row(
children: [
const Expanded(child: Divider()),
diff --git a/lib/core/presentation/widgets/icon_selector_modal.dart b/lib/core/presentation/widgets/icon_selector_modal.dart
index 84d483d5..499e361e 100644
--- a/lib/core/presentation/widgets/icon_selector_modal.dart
+++ b/lib/core/presentation/widgets/icon_selector_modal.dart
@@ -47,8 +47,6 @@ class _IconSelectorModalState extends State {
@override
Widget build(BuildContext context) {
- final AppColors colors = AppColors.of(context);
-
final t = Translations.of(context);
return DraggableScrollableSheet(
@@ -60,7 +58,8 @@ class _IconSelectorModalState extends State {
final iconsByScope = SupportedIconService.instance.getIconsByScope();
return Scaffold(
- backgroundColor: AppColors.of(context).modalBackground,
+ backgroundColor:
+ Theme.of(context).colorSchemeExtended.modalBackground,
body: Column(children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
@@ -79,17 +78,21 @@ class _IconSelectorModalState extends State {
],
),
Chip(
- side: BorderSide(color: colors.primary, width: 2),
- // backgroundColor: AppColors.of(context).primaryLight,
- label: _selectedIcon!
- .display(size: 34, color: colors.onSurface),
+ side: BorderSide(
+ color: Theme.of(context).colorScheme.primary,
+ width: 2),
+ // backgroundColor: Theme.of(context).colorScheme.primaryLight,
+ label: _selectedIcon!.display(
+ size: 34,
+ color: Theme.of(context).colorScheme.onSurface),
)
],
),
),
Expanded(
child: ScrollableWithBottomGradient(
- gradientColor: AppColors.of(context).modalBackground,
+ gradientColor:
+ Theme.of(context).colorSchemeExtended.modalBackground,
controller: scrollController,
child: Column(
children: iconsByScope.keys.toList().map((scope) {
@@ -103,7 +106,9 @@ class _IconSelectorModalState extends State {
Container(
padding: const EdgeInsets.symmetric(
vertical: 0, horizontal: 16),
- color: colors.modalBackground,
+ color: Theme.of(context)
+ .colorSchemeExtended
+ .modalBackground,
child: Text(t[
'icon_selector.scopes.${scope.replaceAll("/", "_")}']),
),
@@ -122,7 +127,9 @@ class _IconSelectorModalState extends State {
: 1,
clipBehavior: Clip.antiAlias,
color: _selectedIcon?.id == e.id
- ? colors.primary
+ ? Theme.of(context)
+ .colorScheme
+ .primary
: null,
child: IconDisplayer(
supportedIcon: e,
@@ -135,8 +142,12 @@ class _IconSelectorModalState extends State {
secondaryColor: Colors.transparent,
isOutline: _selectedIcon?.id == e.id,
mainColor: _selectedIcon?.id == e.id
- ? colors.onPrimary
- : colors.onSurface),
+ ? Theme.of(context)
+ .colorScheme
+ .onPrimary
+ : Theme.of(context)
+ .colorScheme
+ .onSurface),
))
.toList(),
),
diff --git a/lib/core/presentation/widgets/inline_info_card.dart b/lib/core/presentation/widgets/inline_info_card.dart
index 1eba63e9..6c8cba47 100644
--- a/lib/core/presentation/widgets/inline_info_card.dart
+++ b/lib/core/presentation/widgets/inline_info_card.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:monekin/core/extensions/color.extensions.dart';
import 'package:monekin/core/presentation/responsive/responsive_row_column.dart';
-
-import '../app_colors.dart';
+import 'package:monekin/core/presentation/theme.dart';
enum InlineInfoCardMode { warn, info }
@@ -23,14 +23,25 @@ class InlineInfoCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final Color color = mode == InlineInfoCardMode.info
- ? AppColors.of(context).primary
- : Colors.amber;
+ final isDarkBrightness = isAppInDarkBrightness(context);
+
+ final Color bgColor = mode == InlineInfoCardMode.warn
+ ? Colors.amber.darken(isDarkBrightness ? 0.6 : -0.7)
+ : Theme.of(context).colorScheme.primaryContainer;
+ final Color iconColor = mode == InlineInfoCardMode.warn
+ ? Colors.amber.lighten(isDarkBrightness ? 0.5 : -0.4)
+ : Theme.of(context).colorScheme.onPrimaryContainer;
+
+ // final iconColor = baseColor.lighten(isDarkBrightness ? 0.5 : -0.4);
return Card(
- // color: color.withOpacity(0.1),
- elevation: 1,
+ color: bgColor,
+ elevation: 0,
margin: margin,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ side: BorderSide(width: 2, color: iconColor),
+ ),
child: ResponsiveRowColumn.withSymetricSpacing(
spacing: 10,
padding: const EdgeInsets.all(8),
@@ -41,7 +52,7 @@ class InlineInfoCard extends StatelessWidget {
mode == InlineInfoCardMode.info
? Icons.info_rounded
: Icons.warning_rounded,
- color: color,
+ color: iconColor,
size: 28,
),
),
@@ -52,10 +63,9 @@ class InlineInfoCard extends StatelessWidget {
textAlign: direction == Axis.vertical
? TextAlign.center
: TextAlign.left,
- style: const TextStyle(
+ style: TextStyle(
fontSize: 12.25,
fontWeight: FontWeight.w400,
- //color: Theme.of(context).colorScheme.onPrimary,
),
),
),
diff --git a/lib/core/presentation/widgets/no_results.dart b/lib/core/presentation/widgets/no_results.dart
index 6e392b74..fc6a4db4 100644
--- a/lib/core/presentation/widgets/no_results.dart
+++ b/lib/core/presentation/widgets/no_results.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
-import 'package:monekin/core/presentation/animations/shake/fade_in.dart';
+import 'package:monekin/core/presentation/animations/fade_in.dart';
import 'package:monekin/core/presentation/app_colors.dart';
import 'package:monekin/core/presentation/theme.dart';
@@ -51,7 +51,10 @@ class NoResults extends StatelessWidget {
: 'assets/icons/page_states/empty_folder.svg',
colorFilter: ColorFilter.mode(
tintColor == null
- ? AppColors.of(context).primary.withOpacity(0.7)
+ ? Theme.of(context)
+ .colorScheme
+ .primary
+ .withOpacity(0.7)
: tintColor!.withOpacity(0.7),
BlendMode.srcIn,
),
diff --git a/lib/core/presentation/widgets/tappable.dart b/lib/core/presentation/widgets/tappable.dart
index 4fbc7829..a1c5c741 100644
--- a/lib/core/presentation/widgets/tappable.dart
+++ b/lib/core/presentation/widgets/tappable.dart
@@ -14,7 +14,8 @@ class Tappable extends StatelessWidget {
});
final Color? bgColor;
- final double? borderRadius;
+ final BorderRadius? borderRadius;
+
final ShapeBorder? shape;
final EdgeInsets? margin;
@@ -31,18 +32,16 @@ class Tappable extends StatelessWidget {
margin: margin,
child: Material(
color: bgColor,
- borderRadius:
- borderRadius == null ? null : BorderRadius.circular(borderRadius!),
+ borderRadius: borderRadius,
shape: shape,
child: InkWell(
- onTap: onTap,
- onLongPress: onLongPress,
- onDoubleTap: onDoubleTap,
- customBorder: shape,
- borderRadius: borderRadius == null
- ? null
- : BorderRadius.circular(borderRadius!),
- child: child),
+ onTap: onTap,
+ onLongPress: onLongPress,
+ onDoubleTap: onDoubleTap,
+ customBorder: shape,
+ borderRadius: borderRadius,
+ child: child,
+ ),
),
);
}
diff --git a/lib/core/presentation/widgets/transaction_filter/filter_sheet_modal.dart b/lib/core/presentation/widgets/transaction_filter/filter_sheet_modal.dart
index 75f16df9..2be517f2 100644
--- a/lib/core/presentation/widgets/transaction_filter/filter_sheet_modal.dart
+++ b/lib/core/presentation/widgets/transaction_filter/filter_sheet_modal.dart
@@ -126,7 +126,8 @@ class _FilterSheetModalState extends State {
body: ScrollableWithBottomGradient(
controller: scrollController,
padding: const EdgeInsets.fromLTRB(16, 2, 16, 24),
- gradientColor: AppColors.of(context).modalBackground,
+ gradientColor:
+ Theme.of(context).colorSchemeExtended.modalBackground,
child: Form(
key: _formKey,
child: Column(
@@ -212,7 +213,7 @@ class _FilterSheetModalState extends State {
.map((e) =>
e == null ? t.tags.without_tags : e.name)
.printFormatted()
- : t.account.select.all,
+ : t.tags.select.all,
onTap: () => showTagListModal(
context,
modal: TagSelector(
diff --git a/lib/core/presentation/widgets/transaction_filter/tags_filter/tags_filter_container.dart b/lib/core/presentation/widgets/transaction_filter/tags_filter/tags_filter_container.dart
deleted file mode 100644
index 66f2a51c..00000000
--- a/lib/core/presentation/widgets/transaction_filter/tags_filter/tags_filter_container.dart
+++ /dev/null
@@ -1,47 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:monekin/i18n/translations.g.dart';
-
-class TagsFilterContainer extends StatelessWidget {
- const TagsFilterContainer(
- {super.key,
- required this.child,
- this.headerLabelStyle,
- this.headerSpacing = 4});
-
- final Widget child;
-
- final TextStyle? headerLabelStyle;
-
- /// Space between the header label and the tags chips. Defaults to `4`
- final double headerSpacing;
-
- @override
- Widget build(BuildContext context) {
- final t = Translations.of(context);
-
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '${t.tags.display(n: 19)}:',
- style: headerLabelStyle,
- ),
- SizedBox(height: headerSpacing),
- ConstrainedBox(
- constraints: const BoxConstraints(
- maxHeight: 100,
- maxWidth: double.infinity,
- minWidth: double.infinity,
- ),
- child: Card(
- elevation: 0,
- margin: const EdgeInsets.all(0),
- child: SingleChildScrollView(
- padding: const EdgeInsets.all(0),
- child: child,
- ),
- ))
- ],
- );
- }
-}
diff --git a/lib/core/presentation/widgets/trending_value.dart b/lib/core/presentation/widgets/trending_value.dart
index d2ea7907..6c9d3632 100644
--- a/lib/core/presentation/widgets/trending_value.dart
+++ b/lib/core/presentation/widgets/trending_value.dart
@@ -6,40 +6,45 @@ import 'package:monekin/core/presentation/widgets/number_ui_formatters/ui_number
import '../app_colors.dart';
class TrendingValue extends StatelessWidget {
- const TrendingValue(
- {super.key,
- required this.percentage,
- this.decimalDigits = 2,
- this.fontSize = 14,
- this.fontWeight = FontWeight.normal,
- this.filled = false,
- this.outlined = false});
+ const TrendingValue({
+ super.key,
+ required this.percentage,
+ this.decimalDigits = 2,
+ this.fontSize = 14,
+ this.fontWeight = FontWeight.normal,
+ this.filled = false,
+ this.outlined = false,
+ this.markNanAsZero = true,
+ });
final double percentage;
final int decimalDigits;
-
final double fontSize;
-
final FontWeight fontWeight;
-
- final bool filled, outlined;
+ final bool filled, outlined, markNanAsZero;
Widget paintTrendValue(BuildContext context) {
- final textColor =
- _getColorBasedOnPercentage(context).lighten(filled ? 0.85 : 0);
+ final textColor = _getColorBasedOnPercentage(context)
+ .lighten(filled && !outlined ? 0.85 : 0);
+
+ double toDisplay = percentage;
+
+ if (toDisplay.isNaN && markNanAsZero) {
+ toDisplay = 0;
+ }
return Row(
mainAxisSize: MainAxisSize.min,
children: [
- if (percentage != 0)
+ if (toDisplay != 0)
Icon(
- percentage > 0
+ toDisplay > 0
? Icons.trending_up_rounded
: Icons.trending_down_rounded,
size: fontSize * (9 / 7),
color: textColor,
),
- if (percentage == 0)
+ if (toDisplay == 0)
Text(
'=',
style: TextStyle(
@@ -51,7 +56,7 @@ class TrendingValue extends StatelessWidget {
),
const SizedBox(width: 6),
UINumberFormatter.percentage(
- amountToConvert: percentage,
+ amountToConvert: toDisplay,
integerStyle: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
@@ -63,10 +68,14 @@ class TrendingValue extends StatelessWidget {
}
Color _getColorBasedOnPercentage(BuildContext context) {
- return percentage == 0
+ return (percentage == 0 || percentage.isNaN)
? AppColors.of(context)
.brand
- .lighten(isAppInDarkBrightness(context) ? 0.45 : 0.25)
+ .lighten(filled
+ ? 0
+ : isAppInDarkBrightness(context)
+ ? 0.45
+ : 0.25)
.withBlue(225)
: percentage > 0
? AppColors.of(context).success
@@ -75,22 +84,23 @@ class TrendingValue extends StatelessWidget {
@override
Widget build(BuildContext context) {
- if (filled) {
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: !filled ? null : (_getColorBasedOnPercentage(context)),
- borderRadius: BorderRadius.circular(9999),
- border: outlined
- ? Border.all(
- color: _getColorBasedOnPercentage(context).lighten(0.85),
- width: 1)
- : null,
- ),
- child: paintTrendValue(context),
- );
- } else {
- return paintTrendValue(context);
- }
+ final trendColor = _getColorBasedOnPercentage(context);
+ final textColor = _getColorBasedOnPercentage(context)
+ .lighten(filled && !outlined ? 0.85 : 0);
+ final backgroundColor = filled && outlined
+ ? trendColor.lighten(0.85).withOpacity(0.95)
+ : filled
+ ? trendColor
+ : null;
+
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
+ decoration: BoxDecoration(
+ color: backgroundColor,
+ borderRadius: BorderRadius.circular(9999),
+ border: outlined ? Border.all(color: textColor, width: 1) : null,
+ ),
+ child: paintTrendValue(context),
+ );
}
}
diff --git a/lib/core/presentation/widgets/user_avatar.dart b/lib/core/presentation/widgets/user_avatar.dart
index 00c02583..02a786fb 100644
--- a/lib/core/presentation/widgets/user_avatar.dart
+++ b/lib/core/presentation/widgets/user_avatar.dart
@@ -3,14 +3,19 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:monekin/core/presentation/widgets/skeleton.dart';
class UserAvatar extends StatelessWidget {
- const UserAvatar({super.key, this.avatar, this.size = 36, this.border});
+ const UserAvatar(
+ {super.key,
+ this.avatar,
+ this.size = 36,
+ this.border,
+ this.backgroundColor});
final String? avatar;
-
final Border? border;
-
final double size;
+ final Color? backgroundColor;
+
@override
Widget build(BuildContext context) {
final ColorScheme colors = Theme.of(context).colorScheme;
@@ -20,14 +25,15 @@ class UserAvatar extends StatelessWidget {
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
- color: colors.primaryContainer,
+ color: backgroundColor ?? colors.primaryContainer,
border: border,
),
child: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(100),
- color: colors.primaryContainer),
+ borderRadius: BorderRadius.circular(100),
+ color: backgroundColor ?? colors.primaryContainer,
+ ),
child: Builder(builder: (context) {
if (avatar == null) {
return const Skeleton(width: 36, height: 36, applyMarging: false);
diff --git a/lib/core/routes/destinations.dart b/lib/core/routes/destinations.dart
index 1a396177..266567e5 100644
--- a/lib/core/routes/destinations.dart
+++ b/lib/core/routes/destinations.dart
@@ -34,10 +34,12 @@ class MainMenuDestination {
final Widget destination;
- NavigationDestination toNavigationDestinationWidget() {
+ NavigationDestination toNavigationDestinationWidget(BuildContext context) {
return NavigationDestination(
icon: Icon(icon),
- selectedIcon: Icon(selectedIcon ?? icon),
+ selectedIcon: Icon(
+ selectedIcon ?? icon,
+ ),
label: label,
);
}
diff --git a/lib/core/services/supported_icon/getter/supported_icons.dart b/lib/core/services/supported_icon/getter/supported_icons.dart
index e7bcf444..5c16b87d 100644
--- a/lib/core/services/supported_icon/getter/supported_icons.dart
+++ b/lib/core/services/supported_icon/getter/supported_icons.dart
@@ -3,4 +3,4 @@
// To re-generate it, please check the python script under services/utils/icon_downloader
/// List of all the supported icons in the app. This list does not take into account app functionality icons, just the icons that are selectable to identificate some accounts, categories...
-List