From fce1f2433b66c68a09cce7f950de7b5d79bac17a Mon Sep 17 00:00:00 2001 From: enriqueloz88 Date: Sun, 11 Feb 2024 00:10:24 +0100 Subject: [PATCH 1/5] feat(ux/ui): Dashboard redesign --- lib/app/home/dashboard.page.dart | 566 +++++++++++------- .../home/widgets/income_or_expense_card.dart | 2 +- 2 files changed, 356 insertions(+), 212 deletions(-) diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index 908ee296..0247c92c 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -7,9 +7,8 @@ import 'package:monekin/app/stats/widgets/balance_bar_chart_small.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/app/transactions/widgets/transaction_list.dart'; import 'package:monekin/core/database/services/account/account_service.dart'; -import 'package:monekin/core/database/services/transaction/transaction_service.dart'; +import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; import 'package:monekin/core/models/account/account.dart'; import 'package:monekin/core/models/date-utils/date_period_state.dart'; import 'package:monekin/core/models/transaction/transaction.dart'; @@ -19,8 +18,10 @@ import 'package:monekin/core/presentation/widgets/card_with_header.dart'; import 'package:monekin/core/presentation/widgets/dates/date_period_modal.dart'; import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart'; import 'package:monekin/core/presentation/widgets/skeleton.dart'; +import 'package:monekin/core/presentation/widgets/tappable.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; import 'package:monekin/core/presentation/widgets/trending_value.dart'; +import 'package:monekin/core/presentation/widgets/user_avatar.dart'; import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/core/routes/destinations.dart'; import 'package:monekin/core/services/finance_health_service.dart'; @@ -29,7 +30,6 @@ import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class DashboardPage extends StatefulWidget { const DashboardPage({super.key}); @@ -55,56 +55,7 @@ class _DashboardPageState extends State { BreakPoint.of(context).isLargerOrEqualTo(BreakpointID.md); return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: !hideDrawerAndFloatingButton, - title: const Text('Monekin'), - elevation: 1, - centerTitle: !hideDrawerAndFloatingButton, - actions: [ - if (BreakPoint.of(context).isLargerOrEqualTo(BreakpointID.md)) - Padding( - padding: const EdgeInsets.only(right: 16), - child: ActionChip( - label: Text(dateRangeService.getText(context)), - backgroundColor: - AppColors.of(context).primaryContainer.darken(), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), - side: BorderSide( - color: AppColors.of(context).onPrimary, - ), - ), - onPressed: () { - openDatePeriodModal( - context, - DatePeriodModal( - initialDatePeriod: dateRangeService.datePeriod, - )).then((_) => setState(() {})); - }, - ), - ), - if (BreakPoint.of(context).isSmallerThan(BreakpointID.md)) - IconButton( - onPressed: () { - openDatePeriodModal( - context, - DatePeriodModal( - initialDatePeriod: dateRangeService.datePeriod, - ), - ).then((value) { - if (value == null) return; - - setState(() { - dateRangeService = dateRangeService.copyWith( - periodModifier: 0, - datePeriod: value, - ); - }); - }); - }, - icon: const Icon(Icons.calendar_today)) - ], - ), + appBar: EmptyAppBar(color: AppColors.of(context).light), floatingActionButton: hideDrawerAndFloatingButton ? null : const NewTransactionButton(), drawer: hideDrawerAndFloatingButton @@ -119,7 +70,7 @@ class _DashboardPageState extends State { onDestinationSelected: (e) { Navigator.pop(context); - context.router.push(drawerItems.elementAt(e).destination); +// context.router.push(drawerItems.elementAt(e).destination); }, selectedIndex: -1, ); @@ -131,182 +82,194 @@ class _DashboardPageState extends State { DefaultTextStyle.merge( style: TextStyle( color: Theme.of(context).appBarTheme.foregroundColor), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).appBarTheme.backgroundColor, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(16), - bottomRight: Radius.circular(16), - )), - padding: EdgeInsets.fromLTRB( - 16, - BreakPoint.of(context).isLargerThan(BreakpointID.md) - ? 8 - : 24, - 16, - 8), - child: ResponsiveRowColumn( - direction: - BreakPoint.of(context).isLargerThan(BreakpointID.md) - ? Axis.horizontal - : Axis.vertical, - rowSpacing: 40, - columnSpacing: 18, - children: [ - ResponsiveRowColumnItem( - child: StreamBuilder( - stream: AccountService.instance.getAccounts(), - builder: (context, accounts) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: BreakPoint.of(context) - .isLargerThan(BreakpointID.md) - ? CrossAxisAlignment.start - : CrossAxisAlignment.center, - children: [ - Text( - '${t.home.total_balance} - ${dateRangeService.getText(context)}', - style: const TextStyle(fontSize: 12), - ), - if (!accounts.hasData) ...[ - const Skeleton(width: 70, height: 40), - const Skeleton(width: 30, height: 14), - ], - if (accounts.hasData) ...[ + 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: [ + Row( + children: [ + StreamBuilder( + stream: UserSettingService.instance + .getSetting(SettingKey.avatar), + builder: (context, snapshot) { + return UserAvatar(avatar: snapshot.data); + }), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Welcome again!", + style: + Theme.of(context).textTheme.labelSmall!, + ), StreamBuilder( - stream: accountService.getAccountsMoney( - accountIds: - accounts.data!.map((e) => e.id)), - builder: (context, snapshot) { - if (snapshot.hasData) { - return CurrencyDisplayer( - amountToConvert: snapshot.data!, - integerStyle: const TextStyle( - fontSize: 40, - fontWeight: FontWeight.w600), - ); - } - - return const Skeleton( - width: 90, height: 40); - }), - if (dateRangeService.startDate != null && - dateRangeService.endDate != null) - StreamBuilder( - stream: accountService - .getAccountsMoneyVariation( - accounts: accounts.data!, - startDate: - dateRangeService.startDate, - endDate: dateRangeService.endDate, - convertToPreferredCurrency: true), + stream: UserSettingService.instance + .getSetting(SettingKey.userName), builder: (context, snapshot) { if (!snapshot.hasData) { return const Skeleton( - width: 52, height: 22); + width: 70, height: 12); } - return TrendingValue( - percentage: snapshot.data!, - filled: true, - fontWeight: FontWeight.bold, - outlined: true, + return Text( + snapshot.data!, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + fontWeight: FontWeight.bold), ); - }, - ), - ] - ], - ); - }), - ), - ResponsiveRowColumnItem( - child: ResponsiveRowColumn( - direction: - BreakPoint.of(context).isLargerThan(BreakpointID.md) - ? Axis.vertical - : Axis.horizontal, - rowMainAxisAlignment: MainAxisAlignment.spaceBetween, - columnCrossAxisAlignment: CrossAxisAlignment.start, - children: [ - ResponsiveRowColumnItem( - child: IncomeOrExpenseCard( - type: TransactionType.income, - startDate: dateRangeService.startDate, - endDate: dateRangeService.endDate, - ), + }), + ], + ) + ], ), - ResponsiveRowColumnItem( - child: IncomeOrExpenseCard( - type: TransactionType.expense, - startDate: dateRangeService.startDate, - endDate: dateRangeService.endDate, + ActionChip( + label: Text(dateRangeService.getText(context)), + backgroundColor: + AppColors.of(context).primaryContainer.darken(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + side: BorderSide( + 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: 24), + StreamBuilder( + stream: AccountService.instance.getAccounts(), + builder: (context, accounts) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.home.total_balance, + style: + Theme.of(context).textTheme.labelSmall!, + ), + if (!accounts.hasData) ...[ + const Skeleton(width: 70, height: 40), + const Skeleton(width: 30, height: 14), + ], + if (accounts.hasData) ...[ + StreamBuilder( + stream: accountService.getAccountsMoney( + accountIds: accounts.data! + .map((e) => e.id)), + builder: (context, snapshot) { + if (snapshot.hasData) { + return CurrencyDisplayer( + amountToConvert: snapshot.data!, + integerStyle: const TextStyle( + fontSize: 32, + fontWeight: FontWeight.w600, + ), + ); + } + + return const Skeleton( + width: 90, height: 40); + }), + if (dateRangeService.startDate != null && + dateRangeService.endDate != null) + StreamBuilder( + stream: accountService + .getAccountsMoneyVariation( + accounts: accounts.data!, + startDate: + dateRangeService.startDate, + endDate: + dateRangeService.endDate, + convertToPreferredCurrency: + true), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Skeleton( + width: 52, height: 22); + } + + return TrendingValue( + percentage: snapshot.data!, + filled: true, + fontWeight: FontWeight.bold, + outlined: true, + ); + }, + ), + ] + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IncomeOrExpenseCard( + type: TransactionType.income, + startDate: dateRangeService.startDate, + endDate: dateRangeService.endDate, + ), + IncomeOrExpenseCard( + type: TransactionType.expense, + startDate: dateRangeService.startDate, + endDate: dateRangeService.endDate, + ), + ], + ), + ], + ); + }, + ), + ], + ), ), ), ), + + _HorizontalScrollableAccountList( + dateRangeService: dateRangeService), + + // ------------- STATS GENERAL CARDS -------------- + Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 24), child: Column( children: [ - StreamBuilder( - stream: AccountService.instance.getAccounts( - predicate: (acc, curr) => acc.closingDate.isNull(), - ), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return CardWithHeader( - title: t.home.active_accounts, - body: const LinearProgressIndicator()); - } else { - final accounts = snapshot.data!; - - return CardWithHeader( - title: t.home.active_accounts, - headerButtonIcon: Icons.add_rounded, - onHeaderButtonClick: accounts.isEmpty - ? null - : () { - context.pushRoute(AccountFormRoute()); - }, - body: buildAccountList(accounts)); - } - }), - const SizedBox(height: 16), - StreamBuilder( - stream: TransactionService.instance - .checkIfCreateTransactionIsPossible(), - builder: (context, accountSnapshot) { - return CardWithHeader( - title: t.home.last_transactions, - onHeaderButtonClick: - accountSnapshot.hasData && accountSnapshot.data! - ? () { - context.pushRoute(TransactionsRoute()); - } - : null, - body: TransactionListComponent( - filters: TransactionFilters( - minDate: dateRangeService.startDate, - maxDate: dateRangeService.endDate, - ), - limit: 5, - showGroupDivider: false, - prevPage: const DashboardPage(), - onEmptyList: Padding( - padding: const EdgeInsets.all(24), - child: Text( - t.transaction.list.empty, - textAlign: TextAlign.center, - ), - ), - )); - }), - const SizedBox(height: 16), ResponsiveRowColumn.withSymetricSpacing( spacing: 16, direction: @@ -482,3 +445,184 @@ class _DashboardPageState extends State { ); } } + +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: 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: () => context.pushRoute( + AccountDetailsRoute(account: account), + ), + 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: '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, + ); + }), + ], + ), + ], + ), + ), + ), + ), + ); + }), + Opacity( + opacity: 0.6, + child: Tappable( + // bgColor: AppColors.of(context).light, + onTap: () { + context.pushRoute(AccountFormRoute()); + }, + 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), + ], + ), + ), + ), + ), + ), + ) + ], + ); + }, + ), + ), + ); + } +} + +class EmptyAppBar extends StatelessWidget implements PreferredSizeWidget { + final Color color; + + const EmptyAppBar({required this.color, super.key}); + + @override + Widget build(BuildContext context) { + return Container( + color: color, + ); + } + + @override + Size get preferredSize => const Size(0.0, 0.0); +} diff --git a/lib/app/home/widgets/income_or_expense_card.dart b/lib/app/home/widgets/income_or_expense_card.dart index 6e680884..fd65d128 100644 --- a/lib/app/home/widgets/income_or_expense_card.dart +++ b/lib/app/home/widgets/income_or_expense_card.dart @@ -24,7 +24,7 @@ class IncomeOrExpenseCard extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), child: Row( children: [ Container( From 4de5261082f61e1daf3ce68abf2da13b578f13dd Mon Sep 17 00:00:00 2001 From: enriqueloz88 Date: Tue, 13 Feb 2024 12:41:07 +0100 Subject: [PATCH 2/5] feat: Introduce tabbed navigation --- lib/app/home/dashboard.page.dart | 1 + lib/app/home/main_layout.dart | 201 ++++++++++-------- lib/app/home/main_layout_observer.dart | 4 +- .../widgets/new_transaction_fl_button.dart | 1 + lib/app/transactions/transactions.page.dart | 2 +- .../services/category/category_service.dart | 4 +- lib/core/routes/app_router.dart | 55 ++--- lib/core/routes/destinations.dart | 56 ++--- lib/i18n/translations.g.dart | 2 +- 9 files changed, 181 insertions(+), 145 deletions(-) diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index 0247c92c..befd854f 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -30,6 +30,7 @@ import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/app_colors.dart'; +@RoutePage() class DashboardPage extends StatefulWidget { const DashboardPage({super.key}); diff --git a/lib/app/home/main_layout.dart b/lib/app/home/main_layout.dart index bfd9bc31..2418e4c5 100644 --- a/lib/app/home/main_layout.dart +++ b/lib/app/home/main_layout.dart @@ -11,7 +11,7 @@ import 'package:monekin/core/routes/destinations.dart'; @RoutePage() class MainLayoutPage extends StatefulWidget { - const MainLayoutPage({Key? key}) : super(key: key); + const MainLayoutPage({super.key}); @override State createState() => _MainLayoutPageState(); @@ -33,108 +33,127 @@ class _MainLayoutPageState extends State { (element) => element.destination.routeName == snapshot.data); return BreakpointContainer( - mdChild: SafeArea( - child: Row( - children: [ - NavigationRail( - leading: const Column( - children: [ - SizedBox(height: 16), - NewTransactionButton(), - SizedBox(height: 16), - ], - ), - trailing: Expanded( - child: Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: - const EdgeInsets.only(bottom: 16, top: 16), - child: StreamBuilder( - stream: UserSettingService.instance - .getSetting(SettingKey.avatar), - builder: (context, snapshot) { - return UserAvatar(avatar: snapshot.data); - }), + mdChild: SafeArea( + child: Row( + children: [ + NavigationRail( + leading: const Column( + children: [ + SizedBox(height: 16), + NewTransactionButton(), + SizedBox(height: 16), + ], + ), + trailing: Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only( + bottom: 16, top: 16), + child: StreamBuilder( + stream: UserSettingService.instance + .getSetting(SettingKey.avatar), + builder: (context, snapshot) { + return UserAvatar( + avatar: snapshot.data); + }), + ), ), ), - ), - labelType: NavigationRailLabelType.all, - destinations: menuItems - .map((e) => e.toNavigationRailDestinationWidget()) - .toList(), + labelType: NavigationRailLabelType.all, + destinations: menuItems + .map((e) => + e.toNavigationRailDestinationWidget()) + .toList(), + onDestinationSelected: (e) { + if (e == selectedNavItemIndex) return; + + AutoRouter.of(context) + .push(menuItems.elementAt(e).destination); + }, + selectedIndex: selectedNavItemIndex < 0 + ? null + : selectedNavItemIndex), + const VerticalDivider(thickness: 2, width: 1), + const _RightWidgetContainer() + ], + ), + ), + xlChild: SafeArea( + child: Row( + children: [ + HomeDrawer( + drawerActions: menuItems, onDestinationSelected: (e) { if (e == selectedNavItemIndex) return; AutoRouter.of(context) - .push(menuItems.elementAt(e).destination); + .navigate(menuItems.elementAt(e).destination); }, - selectedIndex: selectedNavItemIndex < 0 - ? null - : selectedNavItemIndex), - const VerticalDivider(thickness: 2, width: 1), - Expanded( - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.all(16), - child: Card( - elevation: 4, - clipBehavior: Clip.hardEdge, - child: AutoRouter( - key: mainLayoutRouterKey, - navigatorObservers: () => - [MainLayoutNavObserver()], - ), - ), + selectedIndex: selectedNavItemIndex, ), - ) - ], + const _RightWidgetContainer() + ], + ), ), - ), - xlChild: SafeArea( - child: Row( - children: [ - HomeDrawer( - drawerActions: menuItems, - onDestinationSelected: (e) { - if (e == selectedNavItemIndex) return; + child: AutoTabsRouter( + key: tabsLayoutKey, - AutoRouter.of(context) - .push(menuItems.elementAt(e).destination); - }, - selectedIndex: selectedNavItemIndex, - ), - Expanded( - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.all(16), - child: Card( - elevation: 4, - clipBehavior: Clip.hardEdge, - child: AutoRouter( - key: mainLayoutRouterKey, - navigatorObservers: () => - [MainLayoutNavObserver()], - ), - ), - ), - ) - ], - ), - ), - child: AutoRouter( - key: mainLayoutRouterKey, - navigatorObservers: () => [MainLayoutNavObserver()], - ), - ); + // The routes here should be sub-routes of the MainLayoutRoute + routes: menuItems.map((e) => e.destination).toList(), + transitionBuilder: (context, child, animation) => + FadeTransition( + opacity: animation, + child: child, + ), + builder: (context, child) { + // -- We can also use the context insted of the tabsLayoutKey: + // final tabsRouter = AutoTabsRouter.of(context); + + return Scaffold( + body: child, + bottomNavigationBar: NavigationBar( + selectedIndex: tabsLayoutKey + .currentState!.controller!.activeIndex, + onDestinationSelected: (e) { + tabsLayoutKey.currentState!.controller! + .setActiveIndex(e); + }, + destinations: menuItems + .map((e) => e.toNavigationDestinationWidget()) + .toList(), + )); + }, + )); }), ), ); } } + +class _RightWidgetContainer extends StatelessWidget { + const _RightWidgetContainer({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(16), + child: Card( + elevation: 4, + clipBehavior: Clip.hardEdge, + child: AutoRouter( + key: mainLayoutRouterKey, + navigatorObservers: () => [MainLayoutNavObserver()], + ), + ), + ), + ); + } +} diff --git a/lib/app/home/main_layout_observer.dart b/lib/app/home/main_layout_observer.dart index 418c9fb0..b77f66ab 100644 --- a/lib/app/home/main_layout_observer.dart +++ b/lib/app/home/main_layout_observer.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; final mainLayoutRouterKey = GlobalKey(); +final tabsLayoutKey = GlobalKey(); + StreamController layoutInsideRouteName = BehaviorSubject(); class MainLayoutNavObserver extends RouteObserver> { @@ -18,7 +20,7 @@ class MainLayoutNavObserver extends RouteObserver> { @override void didReplace({Route? newRoute, Route? oldRoute}) { - // print('Main layout nav observer: Did replace'); + //print('Main layout nav observer: Did replace'); layoutInsideRouteName.add(_router?.current.name); } diff --git a/lib/app/home/widgets/new_transaction_fl_button.dart b/lib/app/home/widgets/new_transaction_fl_button.dart index 634109c4..2f630f95 100644 --- a/lib/app/home/widgets/new_transaction_fl_button.dart +++ b/lib/app/home/widgets/new_transaction_fl_button.dart @@ -49,6 +49,7 @@ class NewTransactionButton extends StatelessWidget { } return FloatingActionButton( + heroTag: "new-transaction-floating-button", tooltip: t.transaction.create, onPressed: () => _onPressed(context), child: const Icon(Icons.add_rounded), diff --git a/lib/app/transactions/transactions.page.dart b/lib/app/transactions/transactions.page.dart index 0cb175c5..c7b7b23e 100644 --- a/lib/app/transactions/transactions.page.dart +++ b/lib/app/transactions/transactions.page.dart @@ -59,7 +59,7 @@ class _TransactionsPageState extends State { return PopScope( canPop: !searchActive, onPopInvoked: (didPop) { - // if (didPop) return; + if (didPop) return; if (searchFocusNode.hasFocus && (searchValue != null && searchValue!.isNotEmpty)) { diff --git a/lib/core/database/services/category/category_service.dart b/lib/core/database/services/category/category_service.dart index 1b07633d..427cf224 100644 --- a/lib/core/database/services/category/category_service.dart +++ b/lib/core/database/services/category/category_service.dart @@ -77,7 +77,7 @@ class CategoryService { type: CategoryType.values.byName(category['type'])); await db.customStatement( - "INSERT INTO categories(id, name, iconId, color, type) VALUES ('${categoryToPush.id}', '${categoryToPush.name}', '${categoryToPush.iconId}', '${categoryToPush.color}', '${categoryToPush.type?.name}')"); + "INSERT INTO categories(id, name, iconId, color, type, displayOrder) VALUES ('${categoryToPush.id}', '${categoryToPush.name}', '${categoryToPush.iconId}', '${categoryToPush.color}', '${categoryToPush.type?.name}', ${categoryToPush.displayOrder})"); if (category['subcategories'] != null) { for (final subcategory in category['subcategories']) { @@ -89,7 +89,7 @@ class CategoryService { parentCategoryID: categoryToPush.id); await db.customStatement( - "INSERT INTO categories(id, name, iconId, parentCategoryID) VALUES ('${subcategoryToPush.id}', '${subcategoryToPush.name}', '${subcategoryToPush.iconId}', '${subcategoryToPush.parentCategoryID}')"); + "INSERT INTO categories(id, name, iconId, parentCategoryID, displayOrder) VALUES ('${subcategoryToPush.id}', '${subcategoryToPush.name}', '${subcategoryToPush.iconId}', '${subcategoryToPush.parentCategoryID}', ${subcategoryToPush.displayOrder})"); } } } diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index 2d124731..33f4ddc3 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -59,6 +59,34 @@ class IntroSeenGuard extends AutoRouteGuard { @AutoRouterConfig() class AppRouter extends _$AppRouter { + final _routes = [ + AutoRoute(page: BudgetsRoute.page), + AutoRoute(page: BudgetDetailsRoute.page), + AutoRoute(page: BudgetFormRoute.page), + AutoRoute(page: AllAccountsRoute.page), + AutoRoute(page: TransactionsRoute.page), + AutoRoute(page: RecurrentTransactionRoute.page), + AutoRoute(page: AccountFormRoute.page), + AutoRoute(page: CurrencyManagerRoute.page), + AutoRoute(page: CategoriesListRoute.page), + AutoRoute(page: CategoryFormRoute.page), + AutoRoute(page: IntervalSelectorRoute.page), + AutoRoute(page: ExchangeRateDetailsRoute.page), + AutoRoute(page: TagFormRoute.page), + AutoRoute(page: TagListRoute.page), + AutoRoute(page: AccountDetailsRoute.page), + AutoRoute(page: TransactionFormRoute.page), + AutoRoute(page: TransactionDetailsRoute.page), + AutoRoute(page: StatsRoute.page), + AutoRoute(page: SettingsRoute.page), + AutoRoute(page: AboutRoute.page), + AutoRoute(page: HelpUsRoute.page), + AutoRoute(page: ImportCSVRoute.page), + AutoRoute(page: BackupSettingsRoute.page), + AutoRoute(page: AdvancedSettingsRoute.page), + AutoRoute(page: ExportDataRoute.page), + ]; + @override List get routes => [ AutoRoute(page: IntroRoute.page), @@ -72,32 +100,9 @@ class AppRouter extends _$AppRouter { page: DashboardRoute.page, initial: true, ), - AutoRoute(page: BudgetsRoute.page), - AutoRoute(page: BudgetDetailsRoute.page), - AutoRoute(page: BudgetFormRoute.page), - AutoRoute(page: AllAccountsRoute.page), - AutoRoute(page: TransactionsRoute.page), - AutoRoute(page: RecurrentTransactionRoute.page), - AutoRoute(page: AccountFormRoute.page), - AutoRoute(page: CurrencyManagerRoute.page), - AutoRoute(page: CategoriesListRoute.page), - AutoRoute(page: CategoryFormRoute.page), - AutoRoute(page: IntervalSelectorRoute.page), - AutoRoute(page: ExchangeRateDetailsRoute.page), - AutoRoute(page: TagFormRoute.page), - AutoRoute(page: TagListRoute.page), - AutoRoute(page: AccountDetailsRoute.page), - AutoRoute(page: TransactionFormRoute.page), - AutoRoute(page: TransactionDetailsRoute.page), - AutoRoute(page: StatsRoute.page), - AutoRoute(page: SettingsRoute.page), - AutoRoute(page: AboutRoute.page), - AutoRoute(page: HelpUsRoute.page), - AutoRoute(page: ImportCSVRoute.page), - AutoRoute(page: BackupSettingsRoute.page), - AutoRoute(page: AdvancedSettingsRoute.page), - AutoRoute(page: ExportDataRoute.page), + ..._routes, ], ), + ..._routes ]; } diff --git a/lib/core/routes/destinations.dart b/lib/core/routes/destinations.dart index cd8cab92..ce2033e6 100644 --- a/lib/core/routes/destinations.dart +++ b/lib/core/routes/destinations.dart @@ -1,5 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/core/presentation/responsive/breakpoints.dart'; import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -49,44 +50,51 @@ List getDestinations( }) { final t = Translations.of(context); + final bool isMobileMode = + BreakPoint.of(context).isSmallerThan(BreakpointID.md); + return [ - if (showHome) + if (showHome || isMobileMode) MainMenuDestination( label: t.home.title, icon: Icons.home_outlined, selectedIcon: Icons.home, destination: const DashboardRoute(), ), - MainMenuDestination( - label: t.budgets.title, - icon: Icons.calculate_outlined, - selectedIcon: Icons.calculate, - destination: const BudgetsRoute(), - ), - MainMenuDestination( - label: t.general.accounts, - icon: Icons.account_balance_rounded, - destination: const AllAccountsRoute(), - ), - if (MediaQuery.of(context).size.height > 600) + if (!isMobileMode) ...[ + MainMenuDestination( + label: t.budgets.title, + icon: Icons.calculate_outlined, + selectedIcon: Icons.calculate, + destination: const BudgetsRoute(), + ), + MainMenuDestination( + label: t.general.accounts, + icon: Icons.account_balance_rounded, + destination: const AllAccountsRoute(), + ), + ], + if (MediaQuery.of(context).size.height > 600 || isMobileMode) MainMenuDestination( label: t.transaction.display(n: 10), icon: Icons.app_registration_rounded, destination: TransactionsRoute(), ), - MainMenuDestination( - label: shortLabels - ? t.recurrent_transactions.title_short - : t.recurrent_transactions.title, - icon: Icons.auto_mode_rounded, - destination: const RecurrentTransactionRoute(), - ), - if (MediaQuery.of(context).size.height > 650) + if (!isMobileMode) ...[ MainMenuDestination( - label: t.stats.title, - icon: Icons.auto_graph_rounded, - destination: StatsRoute(), + label: shortLabels + ? t.recurrent_transactions.title_short + : t.recurrent_transactions.title, + icon: Icons.auto_mode_rounded, + destination: const RecurrentTransactionRoute(), ), + if (MediaQuery.of(context).size.height > 650) + MainMenuDestination( + label: t.stats.title, + icon: Icons.auto_graph_rounded, + destination: StatsRoute(), + ), + ], MainMenuDestination( label: t.settings.title, selectedIcon: Icons.settings, diff --git a/lib/i18n/translations.g.dart b/lib/i18n/translations.g.dart index 1a17a2cc..fbf1178c 100644 --- a/lib/i18n/translations.g.dart +++ b/lib/i18n/translations.g.dart @@ -6,7 +6,7 @@ /// Locales: 2 /// Strings: 1026 (513 per locale) /// -/// Built on 2024-02-06 at 18:53 UTC +/// Built on 2024-02-12 at 15:37 UTC // coverage:ignore-file // ignore_for_file: type=lint From fc46c1db973cf514ee3ba238f518dd20db965b8c Mon Sep 17 00:00:00 2001 From: enriqueloz88 Date: Wed, 14 Feb 2024 16:46:23 +0100 Subject: [PATCH 3/5] feat: Remove `auto-route` package --- lib/app/accounts/account_form.dart | 2 - lib/app/accounts/all_accounts_balance.dart | 9 +- lib/app/accounts/all_accounts_page.dart | 19 +- lib/app/accounts/details/account_details.dart | 31 +- .../details/account_details_actions.dart | 12 +- lib/app/budgets/budget_details_page.dart | 11 +- lib/app/budgets/budget_form_page.dart | 2 - lib/app/budgets/budgets_page.dart | 15 +- lib/app/categories/categories_list.dart | 11 +- lib/app/categories/category_selector.dart | 4 - lib/app/categories/form/category_form.dart | 3 +- lib/app/currencies/currency_manager.dart | 9 +- lib/app/currencies/exchange_rate_details.dart | 3 +- lib/app/home/dashboard.page.dart | 48 +- lib/app/home/main_layout.dart | 159 --- lib/app/home/main_layout_observer.dart | 45 - lib/app/home/root_navigator_observer.dart | 21 + .../widgets/new_transaction_fl_button.dart | 10 +- lib/app/layout/lazy_indexed_stack.dart | 80 ++ lib/app/layout/navigation_sidebar.dart | 115 ++ lib/app/layout/tabs.dart | 164 +++ lib/app/onboarding/intro.page.dart | 9 +- lib/app/onboarding/onboarding.dart | 20 +- lib/app/settings/about_page.dart | 2 - .../settings/appearance_settings_page.dart | 2 - lib/app/settings/backup_settings_page.dart | 13 +- lib/app/settings/export_page.dart | 2 - lib/app/settings/help_us_page.dart | 2 - lib/app/settings/import_csv.dart | 7 +- lib/app/settings/settings.page.dart | 30 +- lib/app/stats/stats_page.dart | 2 - .../category_stats_modal.dart | 9 +- lib/app/tags/tag_form_page.dart | 2 - lib/app/tags/tag_list.page.dart | 10 +- .../form/transaction_form.page.dart | 2 - .../form/widgets/interval_selector.dart | 3 +- .../form/widgets/interval_selector_help.dart | 13 +- .../recurrent_transactions_page.dart | 4 +- .../transaction_details.page.dart | 7 +- lib/app/transactions/transactions.page.dart | 15 +- .../widgets/transaction_list.dart | 9 + .../widgets/transaction_list_tile.dart | 33 +- lib/core/routes/app_router.dart | 108 -- lib/core/routes/app_router.gr.dart | 1014 ----------------- lib/core/routes/destinations.dart | 149 ++- lib/core/routes/route_utils.dart | 40 + .../transaction_view_actions_service.dart | 17 +- lib/i18n/translations.g.dart | 2 +- lib/main.dart | 88 +- pubspec.lock | 16 - pubspec.yaml | 2 - 51 files changed, 815 insertions(+), 1590 deletions(-) delete mode 100644 lib/app/home/main_layout.dart delete mode 100644 lib/app/home/main_layout_observer.dart create mode 100644 lib/app/home/root_navigator_observer.dart create mode 100644 lib/app/layout/lazy_indexed_stack.dart create mode 100644 lib/app/layout/navigation_sidebar.dart create mode 100644 lib/app/layout/tabs.dart delete mode 100644 lib/core/routes/app_router.dart delete mode 100644 lib/core/routes/app_router.gr.dart create mode 100644 lib/core/routes/route_utils.dart diff --git a/lib/app/accounts/account_form.dart b/lib/app/accounts/account_form.dart index f3520520..f8a0f86b 100644 --- a/lib/app/accounts/account_form.dart +++ b/lib/app/accounts/account_form.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:drift/drift.dart' as drift; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -25,7 +24,6 @@ import 'package:monekin/core/utils/text_field_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import 'package:uuid/uuid.dart'; -@RoutePage() class AccountFormPage extends StatefulWidget { const AccountFormPage({super.key, this.account}); diff --git a/lib/app/accounts/all_accounts_balance.dart b/lib/app/accounts/all_accounts_balance.dart index 112d971e..28d2cd8b 100644 --- a/lib/app/accounts/all_accounts_balance.dart +++ b/lib/app/accounts/all_accounts_balance.dart @@ -1,16 +1,16 @@ import 'dart:async'; import 'dart:math'; -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/app/accounts/account_form.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/services/account/account_service.dart'; import 'package:monekin/core/models/account/account.dart'; import 'package:monekin/core/presentation/widgets/animated_progress_bar.dart'; import 'package:monekin/core/presentation/widgets/card_with_header.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import '../../core/database/services/currency/currency_service.dart'; @@ -130,8 +130,9 @@ class _AllAccountBalancePageState extends State { return ListTile( leading: accountWithMoney.account.displayIcon(context), - onTap: () => context.pushRoute( - AccountFormRoute( + onTap: () => RouteUtils.pushRoute( + context, + AccountFormPage( account: accountWithMoney.account), ), title: Column( diff --git a/lib/app/accounts/all_accounts_page.dart b/lib/app/accounts/all_accounts_page.dart index 6b573681..a9d14dce 100644 --- a/lib/app/accounts/all_accounts_page.dart +++ b/lib/app/accounts/all_accounts_page.dart @@ -1,16 +1,16 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; 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/presentation/app_colors.dart'; import 'package:monekin/core/presentation/widgets/monekin_reorderable_list.dart'; import 'package:monekin/core/presentation/widgets/tappable.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/widgets/empty_indicator.dart'; -@RoutePage() class AllAccountsPage extends StatelessWidget { const AllAccountsPage({super.key}); @@ -21,9 +21,10 @@ class AllAccountsPage extends StatelessWidget { return Scaffold( appBar: AppBar(title: Text(t.home.my_accounts)), floatingActionButton: FloatingActionButton.extended( + heroTag: UniqueKey(), icon: const Icon(Icons.add_rounded), label: Text(t.account.form.create), - onPressed: () => context.pushRoute(AccountFormRoute()), + onPressed: () => RouteUtils.pushRoute(context, const AccountFormPage()), ), body: StreamBuilder( stream: AccountService.instance.getAccounts(), @@ -51,8 +52,12 @@ class AllAccountsPage extends StatelessWidget { final account = accounts.elementAt(index); return Tappable( - onTap: () => context.pushRoute( - AccountDetailsRoute(account: account), + onTap: () => RouteUtils.pushRoute( + context, + AccountDetailsPage( + account: account, + accountIconHeroTag: + 'all-accounts-page__account-icon-${account.id}'), ), bgColor: AppColors.of(context).light, margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), @@ -77,7 +82,7 @@ class AllAccountsPage extends StatelessWidget { ], ), leading: Hero( - tag: 'account-icon-${account.id}', + tag: 'all-accounts-page__account-icon-${account.id}', child: account.displayIcon(context), ), subtitle: Text( diff --git a/lib/app/accounts/details/account_details.dart b/lib/app/accounts/details/account_details.dart index 034920e9..35a22667 100644 --- a/lib/app/accounts/details/account_details.dart +++ b/lib/app/accounts/details/account_details.dart @@ -1,9 +1,10 @@ -import 'package:auto_route/auto_route.dart'; 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/details/account_details_actions.dart'; +import 'package:monekin/core/routes/route_utils.dart'; +import 'package:monekin/app/transactions/transactions.page.dart'; import 'package:monekin/app/transactions/widgets/transaction_list.dart'; import 'package:monekin/core/database/services/account/account_service.dart'; import 'package:monekin/core/database/services/exchange-rate/exchange_rate_service.dart'; @@ -19,15 +20,19 @@ import 'package:monekin/core/presentation/widgets/modal_container.dart'; import 'package:monekin/core/presentation/widgets/monekin_quick_actions_buttons.dart'; import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/i18n/translations.g.dart'; -@RoutePage() class AccountDetailsPage extends StatefulWidget { - const AccountDetailsPage({super.key, required this.account}); + const AccountDetailsPage({ + super.key, + required this.account, + required this.accountIconHeroTag, + }); final Account account; + final Object? accountIconHeroTag; + @override State createState() => _AccountDetailsPageState(); } @@ -157,8 +162,9 @@ class _AccountDetailsPageState extends State { ], ), Hero( - tag: 'account-icon-${widget.account.id}', - child: account.displayIcon(context, size: 48)), + tag: widget.accountIconHeroTag ?? UniqueKey(), + child: account.displayIcon(context, size: 48), + ), ], ), ), @@ -219,13 +225,16 @@ class _AccountDetailsPageState extends State { CardWithHeader( title: t.home.last_transactions, onHeaderButtonClick: () { - context.pushRoute( - TransactionsRoute( + RouteUtils.pushRoute( + context, + TransactionsPage( filters: TransactionFilters( accountsIDs: [widget.account.id])), ); }, body: TransactionListComponent( + heroTagBuilder: (tr) => + 'account-details-page__tr-icon-${tr.id}', filters: TransactionFilters( status: TransactionStatus.notIn({ TransactionStatus.pending, @@ -235,8 +244,10 @@ class _AccountDetailsPageState extends State { ), limit: 5, showGroupDivider: false, - prevPage: - AccountDetailsPage(account: widget.account), + prevPage: AccountDetailsPage( + account: widget.account, + accountIconHeroTag: + widget.accountIconHeroTag), onEmptyList: Padding( padding: const EdgeInsets.all(24), child: Text( diff --git a/lib/app/accounts/details/account_details_actions.dart b/lib/app/accounts/details/account_details_actions.dart index 5c0ab9a9..aca5547b 100644 --- a/lib/app/accounts/details/account_details_actions.dart +++ b/lib/app/accounts/details/account_details_actions.dart @@ -1,13 +1,13 @@ -import 'package:auto_route/auto_route.dart'; import 'package:drift/drift.dart' as drift; 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/app/transactions/form/transaction_form.page.dart'; import 'package:monekin/core/database/services/account/account_service.dart'; import 'package:monekin/core/models/account/account.dart'; import 'package:monekin/core/models/transaction/transaction.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; -import 'package:monekin/core/routes/app_router.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'; @@ -23,7 +23,8 @@ abstract class AccountDetailsActions { ListTileActionItem( label: t.general.edit, icon: Icons.edit, - onClick: () => context.pushRoute(AccountFormRoute(account: account)), + onClick: () => + RouteUtils.pushRoute(context, AccountFormPage(account: account)), ), ListTileActionItem( label: t.transfer.create, @@ -39,8 +40,9 @@ abstract class AccountDetailsActions { Text(t.transfer.need_two_accounts_warning_message) ]); - navigateToTransferForm() => context.pushRoute( - TransactionFormRoute( + navigateToTransferForm() => RouteUtils.pushRoute( + context, + TransactionFormPage( fromAccount: account, mode: TransactionFormMode.transfer, ), diff --git a/lib/app/budgets/budget_details_page.dart b/lib/app/budgets/budget_details_page.dart index 3d8e989d..6913d0b7 100644 --- a/lib/app/budgets/budget_details_page.dart +++ b/lib/app/budgets/budget_details_page.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:monekin/app/budgets/budget_form_page.dart'; import 'package:monekin/app/budgets/budgets_page.dart'; import 'package:monekin/app/budgets/components/budget_evolution_chart.dart'; import 'package:monekin/app/transactions/widgets/transaction_list.dart'; @@ -17,14 +17,13 @@ import 'package:monekin/core/presentation/widgets/monekin_popup_menu_button.dart import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart'; import 'package:monekin/core/presentation/widgets/skeleton.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; -import 'package:monekin/core/routes/app_router.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'; import '../../core/presentation/app_colors.dart'; import '../../core/presentation/widgets/empty_indicator.dart'; -@RoutePage() class BudgetDetailsPage extends StatefulWidget { const BudgetDetailsPage({super.key, required this.budget}); @@ -94,8 +93,9 @@ class _BudgetDetailsPageState extends State { label: t.budgets.form.edit, icon: Icons.edit, onClick: () { - context.pushRoute( - BudgetFormRoute( + RouteUtils.pushRoute( + context, + BudgetFormPage( prevPage: const BudgetsPage(), budgetToEdit: budget), ); @@ -261,6 +261,7 @@ class _BudgetDetailsPageState extends State { ), SingleChildScrollView( child: TransactionListComponent( + heroTagBuilder: (tr) => 'budgets-page__tr-icon-${tr.id}', filters: TransactionFilters( status: TransactionStatus.notIn({ TransactionStatus.pending, diff --git a/lib/app/budgets/budget_form_page.dart b/lib/app/budgets/budget_form_page.dart index 843f328a..cd94f97f 100644 --- a/lib/app/budgets/budget_form_page.dart +++ b/lib/app/budgets/budget_form_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:monekin/app/accounts/account_selector.dart'; @@ -19,7 +18,6 @@ import 'package:uuid/uuid.dart'; import '../../core/models/account/account.dart'; import '../../core/presentation/widgets/persistent_footer_button.dart'; -@RoutePage() class BudgetFormPage extends StatefulWidget { const BudgetFormPage({super.key, this.budgetToEdit, required this.prevPage}); diff --git a/lib/app/budgets/budgets_page.dart b/lib/app/budgets/budgets_page.dart index 3c2b35b5..81cf3ab3 100644 --- a/lib/app/budgets/budgets_page.dart +++ b/lib/app/budgets/budgets_page.dart @@ -1,24 +1,25 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:monekin/app/budgets/budget_details_page.dart'; +import 'package:monekin/app/budgets/budget_form_page.dart'; import 'package:monekin/core/database/services/budget/budget_service.dart'; import 'package:monekin/core/models/budget/budget.dart'; import 'package:monekin/core/presentation/widgets/animated_progress_bar.dart'; import 'package:monekin/core/presentation/widgets/empty_indicator.dart'; import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart'; import 'package:monekin/core/presentation/widgets/skeleton.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class BudgetsPage extends StatelessWidget { const BudgetsPage({super.key}); Widget buildBudgetCard(BuildContext context, Budget budget) { return InkWell( - onTap: () => context.pushRoute(BudgetDetailsRoute(budget: budget)), + onTap: () => + RouteUtils.pushRoute(context, BudgetDetailsPage(budget: budget)), child: Container( padding: const EdgeInsets.all(16), child: Column( @@ -106,10 +107,12 @@ class BudgetsPage extends StatelessWidget { ), ), floatingActionButton: FloatingActionButton.extended( + heroTag: UniqueKey(), icon: const Icon(Icons.add_rounded), label: Text(t.budgets.form.create), - onPressed: () => context.pushRoute( - BudgetFormRoute(prevPage: const BudgetsPage()), + onPressed: () => RouteUtils.pushRoute( + context, + const BudgetFormPage(prevPage: BudgetsPage()), )), body: TabBarView(children: [ StreamBuilder( diff --git a/lib/app/categories/categories_list.dart b/lib/app/categories/categories_list.dart index bb556879..79542f01 100644 --- a/lib/app/categories/categories_list.dart +++ b/lib/app/categories/categories_list.dart @@ -1,11 +1,11 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/categories/category_selector.dart'; +import 'package:monekin/app/categories/form/category_form.dart'; import 'package:monekin/app/categories/subcategory_selector.dart'; import 'package:monekin/core/database/services/category/category_service.dart'; import 'package:monekin/core/models/category/category.dart'; import 'package:monekin/core/presentation/widgets/persistent_footer_button.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/utils/color_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -38,7 +38,6 @@ Future?> showCategoryListModal( ); } -@RoutePage() class CategoriesListPage extends CategoriesList { const CategoriesListPage() : super(mode: CategoriesListMode.page, selectedCategories: const []); @@ -110,8 +109,8 @@ class _CategoriesListState extends State { return; } - goToCategoryForm() => - context.pushRoute(CategoryFormRoute(categoryUUID: category.id)); + goToCategoryForm() => RouteUtils.pushRoute( + context, CategoryFormPage(categoryUUID: category.id)); if (widget.mode == CategoriesListMode.page) { await goToCategoryForm(); @@ -182,7 +181,7 @@ class _CategoriesListState extends State { PersistentFooterButton( child: FilledButton.icon( onPressed: () { - context.pushRoute(CategoryFormRoute()); + RouteUtils.pushRoute(context, const CategoryFormPage()); }, icon: const Icon(Icons.add), label: Text(t.categories.create), diff --git a/lib/app/categories/category_selector.dart b/lib/app/categories/category_selector.dart index 97dc77ae..8fb9f268 100644 --- a/lib/app/categories/category_selector.dart +++ b/lib/app/categories/category_selector.dart @@ -75,8 +75,6 @@ class _CategorySelectorState extends State { if (!widget.multiSelection) { selectedCategories = [categoryToDisplay]; - print(selectedCategories); - setState(() {}); if (widget.onChange != null) { @@ -137,8 +135,6 @@ class _CategorySelectorState extends State { ), mainColor: AppColors.of(context).onBackground, onTap: () { - print(selectedCategories); - if (selectedCategories == null) { selectedCategories = []; } else { diff --git a/lib/app/categories/form/category_form.dart b/lib/app/categories/form/category_form.dart index 8f9ef1fa..3f486885 100644 --- a/lib/app/categories/form/category_form.dart +++ b/lib/app/categories/form/category_form.dart @@ -1,4 +1,4 @@ -import 'package:auto_route/auto_route.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:drift/drift.dart' as drift; import 'package:flutter/material.dart'; import 'package:monekin/app/categories/form/category_form_functions.dart'; @@ -16,7 +16,6 @@ import 'package:monekin/core/utils/text_field_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import 'package:uuid/uuid.dart'; -@RoutePage() class CategoryFormPage extends StatefulWidget { const CategoryFormPage({super.key, this.categoryUUID}); diff --git a/lib/app/currencies/currency_manager.dart b/lib/app/currencies/currency_manager.dart index 8ee30059..5eef810c 100644 --- a/lib/app/currencies/currency_manager.dart +++ b/lib/app/currencies/currency_manager.dart @@ -1,5 +1,5 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/app/currencies/exchange_rate_details.dart'; import 'package:monekin/app/currencies/exchange_rate_form.dart'; import 'package:monekin/core/database/services/currency/currency_service.dart'; import 'package:monekin/core/database/services/exchange-rate/exchange_rate_service.dart'; @@ -8,12 +8,11 @@ import 'package:monekin/core/models/currency/currency.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; import 'package:monekin/core/presentation/widgets/currency_selector_modal.dart'; import 'package:monekin/core/presentation/widgets/skeleton.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/widgets/empty_indicator.dart'; -@RoutePage() class CurrencyManagerPage extends StatefulWidget { const CurrencyManagerPage({super.key}); @@ -176,8 +175,8 @@ class _CurrencyManagerPageState extends State { if (currency == null) return; - onTapContext.pushRoute( - ExchangeRateDetailsRoute(currency: currency)); + RouteUtils.pushRoute(context, + ExchangeRateDetailsPage(currency: currency)); }, ); }, diff --git a/lib/app/currencies/exchange_rate_details.dart b/lib/app/currencies/exchange_rate_details.dart index 99584513..090351a9 100644 --- a/lib/app/currencies/exchange_rate_details.dart +++ b/lib/app/currencies/exchange_rate_details.dart @@ -1,4 +1,4 @@ -import 'package:auto_route/auto_route.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:monekin/app/currencies/exchange_rate_form.dart'; @@ -10,7 +10,6 @@ import 'package:monekin/core/presentation/widgets/persistent_footer_button.dart' import 'package:monekin/core/utils/list_tile_action_item.dart'; import 'package:monekin/i18n/translations.g.dart'; -@RoutePage() class ExchangeRateDetailsPage extends StatefulWidget { const ExchangeRateDetailsPage({super.key, required this.currency}); diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index befd854f..a1f1de59 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -1,8 +1,11 @@ -import 'package:auto_route/auto_route.dart'; 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/routes/route_utils.dart'; import 'package:monekin/app/home/widgets/home_drawer.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/stats/stats_page.dart'; import 'package:monekin/app/stats/widgets/balance_bar_chart_small.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'; @@ -22,7 +25,6 @@ import 'package:monekin/core/presentation/widgets/tappable.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; import 'package:monekin/core/presentation/widgets/trending_value.dart'; import 'package:monekin/core/presentation/widgets/user_avatar.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/core/routes/destinations.dart'; import 'package:monekin/core/services/finance_health_service.dart'; import 'package:monekin/core/utils/color_utils.dart'; @@ -30,7 +32,6 @@ import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class DashboardPage extends StatefulWidget { const DashboardPage({super.key}); @@ -282,8 +283,8 @@ class _DashboardPageState extends State { rowFit: FlexFit.tight, child: CardWithHeader( title: t.financial_health.display, - onHeaderButtonClick: () => - context.pushRoute(StatsRoute(initialIndex: 0)), + onHeaderButtonClick: () => RouteUtils.pushRoute( + context, const StatsPage(initialIndex: 0)), bodyPadding: const EdgeInsets.only(right: 8), body: StreamBuilder( stream: FinanceHealthService().getHealthyValue( @@ -313,7 +314,8 @@ class _DashboardPageState extends State { dateRange: dateRangeService, ), onHeaderButtonClick: () { - context.pushRoute(StatsRoute(initialIndex: 2)); + RouteUtils.pushRoute( + context, const StatsPage(initialIndex: 2)); }), ), ], @@ -333,7 +335,8 @@ class _DashboardPageState extends State { body: ChartByCategories( datePeriodState: dateRangeService), onHeaderButtonClick: () { - context.pushRoute(StatsRoute(initialIndex: 1)); + RouteUtils.pushRoute( + context, const StatsPage(initialIndex: 1)); }), ), ResponsiveRowColumnItem( @@ -347,7 +350,8 @@ class _DashboardPageState extends State { dateRangeService: dateRangeService), ), onHeaderButtonClick: () { - context.pushRoute(StatsRoute(initialIndex: 3)); + RouteUtils.pushRoute( + context, const StatsPage(initialIndex: 3)); }), ), ], @@ -384,8 +388,8 @@ class _DashboardPageState extends State { ), const SizedBox(height: 8), FilledButton( - onPressed: () => - context.pushRoute(AccountFormRoute()), + onPressed: () => RouteUtils.pushRoute( + context, const AccountFormPage()), child: Text(t.account.form.create)) ], )) @@ -405,10 +409,14 @@ class _DashboardPageState extends State { final account = accounts[index]; return ListTile( - onTap: () => - context.pushRoute(AccountDetailsRoute(account: account)), + onTap: () => RouteUtils.pushRoute( + context, + AccountDetailsPage( + account: account, + accountIconHeroTag: + 'dashboard-page__account-icon-${account.id}')), leading: Hero( - tag: 'account-icon-${account.id}', + tag: 'dashboard-page__account-icon-${account.id}', child: account.displayIcon(context)), trailing: Column( crossAxisAlignment: CrossAxisAlignment.end, @@ -479,8 +487,13 @@ class _HorizontalScrollableAccountList extends StatelessWidget { color: Colors.transparent, elevation: 0, child: Tappable( - onTap: () => context.pushRoute( - AccountDetailsRoute(account: account), + onTap: () => RouteUtils.pushRoute( + context, + AccountDetailsPage( + account: account, + accountIconHeroTag: + 'dashboard-page__account-icon-${account.id}', + ), ), bgColor: AppColors.of(context).light, borderRadius: 12, @@ -492,7 +505,8 @@ class _HorizontalScrollableAccountList extends StatelessWidget { children: [ Row(children: [ Hero( - tag: 'account-icon-${account.id}', + tag: + 'dashboard-page__account-icon-${account.id}', child: account.displayIcon( context, size: 28, @@ -571,7 +585,7 @@ class _HorizontalScrollableAccountList extends StatelessWidget { child: Tappable( // bgColor: AppColors.of(context).light, onTap: () { - context.pushRoute(AccountFormRoute()); + RouteUtils.pushRoute(context, const AccountFormPage()); }, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), diff --git a/lib/app/home/main_layout.dart b/lib/app/home/main_layout.dart deleted file mode 100644 index 2418e4c5..00000000 --- a/lib/app/home/main_layout.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:monekin/app/home/main_layout_observer.dart'; -import 'package:monekin/app/home/widgets/home_drawer.dart'; -import 'package:monekin/app/home/widgets/new_transaction_fl_button.dart'; -import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; -import 'package:monekin/core/presentation/responsive/breakpoint_container.dart'; -import 'package:monekin/core/presentation/responsive/breakpoints.dart'; -import 'package:monekin/core/presentation/widgets/user_avatar.dart'; -import 'package:monekin/core/routes/destinations.dart'; - -@RoutePage() -class MainLayoutPage extends StatefulWidget { - const MainLayoutPage({super.key}); - - @override - State createState() => _MainLayoutPageState(); -} - -class _MainLayoutPageState extends State { - @override - Widget build(BuildContext context) { - final menuItems = getDestinations(context, - shortLabels: BreakPoint.of(context).isSmallerThan(BreakpointID.xl)); - - return HeroControllerScope( - controller: HeroController(), - child: Scaffold( - body: StreamBuilder( - stream: layoutInsideRouteName.stream, - builder: (context, snapshot) { - final selectedNavItemIndex = menuItems.indexWhere( - (element) => element.destination.routeName == snapshot.data); - - return BreakpointContainer( - mdChild: SafeArea( - child: Row( - children: [ - NavigationRail( - leading: const Column( - children: [ - SizedBox(height: 16), - NewTransactionButton(), - SizedBox(height: 16), - ], - ), - trailing: Expanded( - child: Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.only( - bottom: 16, top: 16), - child: StreamBuilder( - stream: UserSettingService.instance - .getSetting(SettingKey.avatar), - builder: (context, snapshot) { - return UserAvatar( - avatar: snapshot.data); - }), - ), - ), - ), - labelType: NavigationRailLabelType.all, - destinations: menuItems - .map((e) => - e.toNavigationRailDestinationWidget()) - .toList(), - onDestinationSelected: (e) { - if (e == selectedNavItemIndex) return; - - AutoRouter.of(context) - .push(menuItems.elementAt(e).destination); - }, - selectedIndex: selectedNavItemIndex < 0 - ? null - : selectedNavItemIndex), - const VerticalDivider(thickness: 2, width: 1), - const _RightWidgetContainer() - ], - ), - ), - xlChild: SafeArea( - child: Row( - children: [ - HomeDrawer( - drawerActions: menuItems, - onDestinationSelected: (e) { - if (e == selectedNavItemIndex) return; - - AutoRouter.of(context) - .navigate(menuItems.elementAt(e).destination); - }, - selectedIndex: selectedNavItemIndex, - ), - const _RightWidgetContainer() - ], - ), - ), - child: AutoTabsRouter( - key: tabsLayoutKey, - - // The routes here should be sub-routes of the MainLayoutRoute - routes: menuItems.map((e) => e.destination).toList(), - transitionBuilder: (context, child, animation) => - FadeTransition( - opacity: animation, - child: child, - ), - builder: (context, child) { - // -- We can also use the context insted of the tabsLayoutKey: - // final tabsRouter = AutoTabsRouter.of(context); - - return Scaffold( - body: child, - bottomNavigationBar: NavigationBar( - selectedIndex: tabsLayoutKey - .currentState!.controller!.activeIndex, - onDestinationSelected: (e) { - tabsLayoutKey.currentState!.controller! - .setActiveIndex(e); - }, - destinations: menuItems - .map((e) => e.toNavigationDestinationWidget()) - .toList(), - )); - }, - )); - }), - ), - ); - } -} - -class _RightWidgetContainer extends StatelessWidget { - const _RightWidgetContainer({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Expanded( - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.all(16), - child: Card( - elevation: 4, - clipBehavior: Clip.hardEdge, - child: AutoRouter( - key: mainLayoutRouterKey, - navigatorObservers: () => [MainLayoutNavObserver()], - ), - ), - ), - ); - } -} diff --git a/lib/app/home/main_layout_observer.dart b/lib/app/home/main_layout_observer.dart deleted file mode 100644 index b77f66ab..00000000 --- a/lib/app/home/main_layout_observer.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; - -final mainLayoutRouterKey = GlobalKey(); -final tabsLayoutKey = GlobalKey(); - -StreamController layoutInsideRouteName = BehaviorSubject(); - -class MainLayoutNavObserver extends RouteObserver> { - StackRouter? get _router => mainLayoutRouterKey.currentState?.controller; - - @override - void didPush(Route route, Route? previousRoute) { - // print('Main layout nav observer: Did push'); - layoutInsideRouteName.add(_router?.current.name); - } - - @override - void didReplace({Route? newRoute, Route? oldRoute}) { - //print('Main layout nav observer: Did replace'); - layoutInsideRouteName.add(_router?.current.name); - } - - @override - void didPop(Route route, Route? previousRoute) { - // print('Main layout nav observer: Did pop'); - layoutInsideRouteName.add(_router?.current.name); - } - - @override - void didRemove(Route route, Route? previousRoute) { - // print('Main layout nav observer: Did remove'); - layoutInsideRouteName.add(_router?.current.name); - } - - @override - void didStartUserGesture( - Route route, Route? previousRoute) {} - - @override - void didStopUserGesture() {} -} diff --git a/lib/app/home/root_navigator_observer.dart b/lib/app/home/root_navigator_observer.dart new file mode 100644 index 00000000..d7b0be8d --- /dev/null +++ b/lib/app/home/root_navigator_observer.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class MainLayoutNavObserver extends RouteObserver> { + @override + void didPush(Route route, Route? previousRoute) { + super.didPush(route, previousRoute); + // print('Main layout nav observer: Did push'); + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + super.didReplace(newRoute: newRoute, oldRoute: oldRoute); + // print('Main layout nav observer: Did replace'); + } + + @override + void didPop(Route route, Route? previousRoute) { + super.didPop(route, previousRoute); + // print('Main layout nav observer: Did pop'); + } +} diff --git a/lib/app/home/widgets/new_transaction_fl_button.dart b/lib/app/home/widgets/new_transaction_fl_button.dart index 2f630f95..e9a1e893 100644 --- a/lib/app/home/widgets/new_transaction_fl_button.dart +++ b/lib/app/home/widgets/new_transaction_fl_button.dart @@ -1,8 +1,9 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/app/accounts/account_form.dart'; +import 'package:monekin/core/routes/route_utils.dart'; +import 'package:monekin/app/transactions/form/transaction_form.page.dart'; import 'package:monekin/core/database/services/transaction/transaction_service.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/i18n/translations.g.dart'; class NewTransactionButton extends StatelessWidget { @@ -21,7 +22,7 @@ class NewTransactionButton extends StatelessWidget { ).then((value) { if (value != true) return; - context.pushRoute(AccountFormRoute()); + RouteUtils.pushRoute(context, AccountFormPage()); }); } @@ -33,7 +34,7 @@ class NewTransactionButton extends StatelessWidget { if (!value) { _showShouldCreateAccountWarn(context); } else { - context.pushRoute(TransactionFormRoute()); + RouteUtils.pushRoute(context, TransactionFormPage()); } }); } @@ -42,6 +43,7 @@ class NewTransactionButton extends StatelessWidget { Widget build(BuildContext context) { if (isExtended) { return FloatingActionButton.extended( + heroTag: null, onPressed: () => _onPressed(context), label: Text(t.transaction.create), icon: const Icon(Icons.add_rounded), diff --git a/lib/app/layout/lazy_indexed_stack.dart b/lib/app/layout/lazy_indexed_stack.dart new file mode 100644 index 00000000..a3f4b352 --- /dev/null +++ b/lib/app/layout/lazy_indexed_stack.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; + +/// {@template lazy_indexed_stack} +/// A widget that displays a [IndexedStack] with lazy loaded children. +/// {@endtemplate} +class LazyIndexedStack extends StatefulWidget { + /// {@macro lazy_indexed_stack} + const LazyIndexedStack({ + super.key, + this.index = 0, + this.children = const [], + this.alignment = AlignmentDirectional.topStart, + this.textDirection, + this.sizing = StackFit.loose, + }); + + /// The index of the child to display. + final int index; + + /// The list of children that can be displayed. + final List children; + + /// How to align the children in the stack. + final AlignmentGeometry alignment; + + /// The direction to use for resolving [alignment]. + final TextDirection? textDirection; + + /// How to size the non-positioned children in the stack. + final StackFit sizing; + + @override + State createState() => _LazyIndexedStackState(); +} + +class _LazyIndexedStackState extends State { + late final List _activatedChildren; + + @override + void initState() { + super.initState(); + _activatedChildren = List.generate( + widget.children.length, + (i) => i == widget.index, + ); + } + + @override + void didUpdateWidget(LazyIndexedStack oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.index != widget.index) _activateChild(widget.index); + } + + void _activateChild(int? index) { + if (index == null) return; + if (!_activatedChildren[index]) _activatedChildren[index] = true; + } + + List get children { + return List.generate( + widget.children.length, + (i) { + return i < _activatedChildren.length && _activatedChildren[i] + ? widget.children[i] + : const SizedBox.shrink(); + }, + ); + } + + @override + Widget build(BuildContext context) { + return IndexedStack( + alignment: widget.alignment, + textDirection: widget.textDirection, + sizing: widget.sizing, + index: widget.index, + children: children, + ); + } +} diff --git a/lib/app/layout/navigation_sidebar.dart b/lib/app/layout/navigation_sidebar.dart new file mode 100644 index 00000000..ec51ae98 --- /dev/null +++ b/lib/app/layout/navigation_sidebar.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:monekin/app/home/widgets/home_drawer.dart'; +import 'package:monekin/app/home/widgets/new_transaction_fl_button.dart'; +import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; +import 'package:monekin/core/presentation/responsive/breakpoint_container.dart'; +import 'package:monekin/core/presentation/responsive/breakpoints.dart'; +import 'package:monekin/core/presentation/widgets/user_avatar.dart'; +import 'package:monekin/core/routes/destinations.dart'; +import 'package:monekin/main.dart'; + +double getNavigationSidebarWidth(BuildContext context) { + if (BreakPoint.of(context).isSmallerThan(BreakpointID.md)) { + return 0; + } + + if (BreakPoint.of(context).isSmallerThan(BreakpointID.xl)) { + return 108; + } + + double screenPercent = 0.3; + double maxWidthNavigation = 270; + + return (MediaQuery.sizeOf(context).width * screenPercent > maxWidthNavigation + ? maxWidthNavigation + : MediaQuery.sizeOf(context).width * screenPercent) + + MediaQuery.viewPaddingOf(context).left; +} + +class NavigationSidebar extends StatefulWidget { + const NavigationSidebar({super.key}); + + @override + State createState() => NavigationSidebarState(); +} + +class NavigationSidebarState extends State { + MainMenuDestination? selectedDestination; + + setSelectedDestination(MainMenuDestination? destination) { + setState(() { + selectedDestination = destination; + }); + } + + @override + Widget build(BuildContext context) { + final menuItems = getDestinations(context, + shortLabels: BreakPoint.of(context).isSmallerThan(BreakpointID.xl)); + + selectedDestination ??= menuItems.elementAt(0); + + final selectedNavItemIndex = menuItems + .indexWhere((element) => element.id == selectedDestination!.id); + + return AnimatedContainer( + duration: const Duration(milliseconds: 1500), + curve: Curves.easeInOutCubicEmphasized, + width: getNavigationSidebarWidth(context), + child: BreakpointContainer( + mdChild: SafeArea( + child: NavigationRail( + leading: const Column( + children: [ + SizedBox(height: 16), + NewTransactionButton(), + SizedBox(height: 16), + ], + ), + trailing: Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only(bottom: 16, top: 16), + child: StreamBuilder( + stream: UserSettingService.instance + .getSetting(SettingKey.avatar), + builder: (context, snapshot) { + return UserAvatar(avatar: snapshot.data); + }), + ), + ), + ), + labelType: NavigationRailLabelType.all, + destinations: menuItems + .map((e) => e.toNavigationRailDestinationWidget()) + .toList(), + onDestinationSelected: (e) => + tabsPageKey.currentState?.changePage(menuItems.elementAt(e)), + selectedIndex: + selectedNavItemIndex < 0 ? null : selectedNavItemIndex), + ), + xlChild: SafeArea( + child: HomeDrawer( + drawerActions: menuItems, + onDestinationSelected: (e) { + // Pop all routes without animation: + navigatorKey.currentState!.pushAndRemoveUntil( + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const SizedBox(), + transitionDuration: const Duration(seconds: 0), + ), + (route) => route.isFirst); + navigatorKey.currentState!.pop(); + + tabsPageKey.currentState?.changePage(menuItems.elementAt(e)); + }, + selectedIndex: selectedNavItemIndex, + ), + ), + child: Container(), + ), + ); + } +} diff --git a/lib/app/layout/tabs.dart b/lib/app/layout/tabs.dart new file mode 100644 index 00000000..49d7b834 --- /dev/null +++ b/lib/app/layout/tabs.dart @@ -0,0 +1,164 @@ +import 'package:flutter/material.dart'; +import 'package:monekin/app/layout/lazy_indexed_stack.dart'; +import 'package:monekin/core/presentation/responsive/breakpoint_container.dart'; +import 'package:monekin/core/presentation/responsive/breakpoints.dart'; +import 'package:monekin/core/routes/destinations.dart'; +import 'package:monekin/main.dart'; + +/// This page is the entry point of the app once the user has complete onboarding +class TabsPage extends StatefulWidget { + const TabsPage({super.key}); + + @override + State createState() => TabsPageState(); +} + +class TabsPageState extends State { + MainMenuDestination? selectedDestination; + + void changePage(MainMenuDestination destination) { + navigationSidebarKey.currentState?.setSelectedDestination(destination); + + setState(() { + selectedDestination = destination; + }); + + FocusScope.of(context).unfocus(); + } + + @override + Widget build(BuildContext context) { + final menuItems = getDestinations(context, + shortLabels: BreakPoint.of(context).isSmallerThan(BreakpointID.xl)); + + selectedDestination ??= menuItems.elementAt(0); + + final selectedNavItemIndex = menuItems + .indexWhere((element) => element.id == selectedDestination!.id); + + return Scaffold( + bottomNavigationBar: BreakPoint.of(context) + .isLargerThan(BreakpointID.sm) || + !(0 <= selectedNavItemIndex && + selectedNavItemIndex < menuItems.length) + ? null + : NavigationBar( + destinations: menuItems + .map((e) => e.toNavigationDestinationWidget()) + .toList(), + selectedIndex: selectedNavItemIndex, + onDestinationSelected: (e) => changePage(menuItems.elementAt(e)), + ), + body: Builder(builder: (context) { + final allDestinations = getAllDestinations(context, + shortLabels: BreakPoint.of(context).isSmallerThan(BreakpointID.xl)); + + return FadeIndexedStack( + index: allDestinations + .indexWhere((element) => element.id == selectedDestination?.id), + duration: const Duration(milliseconds: 300), + children: allDestinations + .map( + (e) => BreakpointContainer( + mdChild: _RightWidgetContainer(child: e.destination), + child: e.destination, + ), + ) + .toList(), + ); + }), + // selectedDestination?.destination ?? const SizedBox.shrink(), + ); + } +} + +class FadeIndexedStack extends StatefulWidget { + final int index; + final List children; + final Duration duration; + final AlignmentGeometry alignment; + final TextDirection? textDirection; + final StackFit sizing; + + const FadeIndexedStack({ + super.key, + required this.index, + required this.children, + this.duration = const Duration( + milliseconds: 250, + ), + this.alignment = AlignmentDirectional.topStart, + this.textDirection, + this.sizing = StackFit.loose, + }); + + @override + FadeIndexedStackState createState() => FadeIndexedStackState(); +} + +class FadeIndexedStackState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller = + AnimationController(vsync: this, duration: widget.duration); + + @override + void didUpdateWidget(FadeIndexedStack oldWidget) { + if (widget.index != oldWidget.index) { + _controller.forward(from: 0.0); + } + super.didUpdateWidget(oldWidget); + } + + @override + void initState() { + _controller.forward(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _controller, + child: LazyIndexedStack( + index: widget.index, + alignment: widget.alignment, + textDirection: widget.textDirection, + sizing: widget.sizing, + children: widget.children, + ), + ); + } +} + +class _RightWidgetContainer extends StatelessWidget { + const _RightWidgetContainer({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Expanded( + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(16), + child: Card( + elevation: 4, + clipBehavior: Clip.hardEdge, + child: child, + ), + ), + ); + } +} diff --git a/lib/app/onboarding/intro.page.dart b/lib/app/onboarding/intro.page.dart index 999e66c5..045f01d9 100644 --- a/lib/app/onboarding/intro.page.dart +++ b/lib/app/onboarding/intro.page.dart @@ -1,13 +1,12 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/app/onboarding/onboarding.dart'; import 'package:monekin/core/presentation/responsive/breakpoint_container.dart'; import 'package:monekin/core/presentation/widgets/html_text.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class IntroPage extends StatelessWidget { const IntroPage({super.key}); @@ -71,7 +70,9 @@ class IntroPage extends StatelessWidget { SizedBox( // width: double.infinity, child: FilledButton.icon( - onPressed: () => context.replaceRoute(const OnboardingRoute()), + onPressed: () => RouteUtils.pushRoute( + context, const OnboardingPage(), + withReplacement: true), icon: const Icon(Icons.person_2_rounded), label: Align( alignment: Alignment.centerLeft, diff --git a/lib/app/onboarding/onboarding.dart b/lib/app/onboarding/onboarding.dart index f930e722..66716301 100644 --- a/lib/app/onboarding/onboarding.dart +++ b/lib/app/onboarding/onboarding.dart @@ -1,19 +1,19 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:introduction_screen/introduction_screen.dart'; +import 'package:monekin/app/layout/tabs.dart'; import 'package:monekin/core/database/services/app-data/app_data_service.dart'; import 'package:monekin/core/database/services/currency/currency_service.dart'; import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; import 'package:monekin/core/presentation/widgets/currency_selector_modal.dart'; import 'package:monekin/core/presentation/widgets/skeleton.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; +import 'package:monekin/main.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class OnboardingPage extends StatefulWidget { const OnboardingPage({super.key}); @@ -25,9 +25,17 @@ class _OnboardingPageState extends State { int currentPage = 0; introFinished() { - AppDataService.instance - .setAppDataItem(AppDataKey.introSeen, '1') - .then((value) => context.replaceRoute(const MainLayoutRoute())); + AppDataService.instance.setAppDataItem(AppDataKey.introSeen, '1').then( + (value) { + RouteUtils.pushRoute( + context, + TabsPage(key: tabsPageKey), + withReplacement: true, + ); + + refresh++; + }, + ); } @override diff --git a/lib/app/settings/about_page.dart b/lib/app/settings/about_page.dart index 544c2586..74e14c5b 100644 --- a/lib/app/settings/about_page.dart +++ b/lib/app/settings/about_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/settings/settings.page.dart'; import 'package:monekin/core/presentation/widgets/skeleton.dart'; @@ -7,7 +6,6 @@ import 'package:monekin/i18n/translations.g.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:slang/builder/utils/string_extensions.dart'; -@RoutePage() class AboutPage extends StatelessWidget { const AboutPage({super.key}); diff --git a/lib/app/settings/appearance_settings_page.dart b/lib/app/settings/appearance_settings_page.dart index 744de62f..e3601b46 100644 --- a/lib/app/settings/appearance_settings_page.dart +++ b/lib/app/settings/appearance_settings_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/settings/settings.page.dart'; import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; @@ -8,7 +7,6 @@ import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class AdvancedSettingsPage extends StatefulWidget { const AdvancedSettingsPage({super.key}); diff --git a/lib/app/settings/backup_settings_page.dart b/lib/app/settings/backup_settings_page.dart index c6d62a61..8929e085 100644 --- a/lib/app/settings/backup_settings_page.dart +++ b/lib/app/settings/backup_settings_page.dart @@ -1,17 +1,17 @@ import 'dart:io'; -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:monekin/core/routes/route_utils.dart'; +import 'package:monekin/app/settings/export_page.dart'; +import 'package:monekin/app/settings/import_csv.dart'; import 'package:monekin/app/settings/settings.page.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/backup/backup_database_service.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/core/utils/number_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; -@RoutePage() class BackupSettingsPage extends StatelessWidget { const BackupSettingsPage({super.key}); @@ -53,7 +53,8 @@ class BackupSettingsPage extends StatelessWidget { return; } - context.router.replaceAll([const MainLayoutRoute()]); + // TODO: REPLACE ALL + // context.router.replaceAll([const MainLayoutRoute()]); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(t.backup.import.success)), @@ -72,7 +73,7 @@ class BackupSettingsPage extends StatelessWidget { subtitle: Text(t.backup.import.manual_import.descr), minVerticalPadding: 16, onTap: () { - context.pushRoute(const ImportCSVRoute()); + RouteUtils.pushRoute(context, const ImportCSVPage()); }, ), createListSeparator(context, t.backup.export.title_short), @@ -81,7 +82,7 @@ class BackupSettingsPage extends StatelessWidget { subtitle: Text(t.backup.export.description), minVerticalPadding: 16, onTap: () { - context.pushRoute(const ExportDataRoute()); + RouteUtils.pushRoute(context, const ExportDataPage()); }, ), createListSeparator(context, t.backup.about.title), diff --git a/lib/app/settings/export_page.dart b/lib/app/settings/export_page.dart index f252dbb5..b14fa958 100644 --- a/lib/app/settings/export_page.dart +++ b/lib/app/settings/export_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/accounts/account_selector.dart'; import 'package:monekin/app/categories/category_selector.dart'; @@ -14,7 +13,6 @@ import '../../core/database/backup/backup_database_service.dart'; enum _ExportFormats { csv, db } -@RoutePage() class ExportDataPage extends StatefulWidget { const ExportDataPage({super.key}); diff --git a/lib/app/settings/help_us_page.dart b/lib/app/settings/help_us_page.dart index 9a9812e1..b2ff6646 100644 --- a/lib/app/settings/help_us_page.dart +++ b/lib/app/settings/help_us_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/settings/purchases/donate_button.dart'; import 'package:monekin/app/settings/purchases/in_app_purchase.dart'; @@ -7,7 +6,6 @@ import 'package:monekin/core/utils/open_external_url.dart'; import 'package:monekin/i18n/translations.g.dart'; import 'package:share_plus/share_plus.dart'; -@RoutePage() class HelpUsPage extends StatelessWidget { const HelpUsPage({super.key}); diff --git a/lib/app/settings/import_csv.dart b/lib/app/settings/import_csv.dart index bf851b8f..a49edabf 100644 --- a/lib/app/settings/import_csv.dart +++ b/lib/app/settings/import_csv.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:drift/drift.dart' as drift; @@ -6,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:monekin/app/accounts/account_selector.dart'; import 'package:monekin/app/categories/categories_list.dart'; +import 'package:monekin/app/layout/tabs.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/backup/backup_database_service.dart'; import 'package:monekin/core/database/services/account/account_service.dart'; @@ -17,7 +17,7 @@ import 'package:monekin/core/models/category/category.dart'; import 'package:monekin/core/models/supported-icon/icon_displayer.dart'; import 'package:monekin/core/models/supported-icon/supported_icon.dart'; import 'package:monekin/core/presentation/widgets/loading_overlay.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/services/supported_icon/supported_icon_service.dart'; import 'package:monekin/core/utils/color_utils.dart'; import 'package:monekin/core/utils/text_field_utils.dart'; @@ -26,7 +26,6 @@ import 'package:uuid/uuid.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class ImportCSVPage extends StatefulWidget { const ImportCSVPage({super.key}); @@ -163,7 +162,7 @@ class _ImportCSVPageState extends State { final loadingOverlay = LoadingOverlay.of(context); onSuccess() { - context.pushRoute(const MainLayoutRoute()); + RouteUtils.pushRoute(context, const TabsPage()); snackbarDisplayer( SnackBar( diff --git a/lib/app/settings/settings.page.dart b/lib/app/settings/settings.page.dart index fd7eeb6c..b79bcd67 100644 --- a/lib/app/settings/settings.page.dart +++ b/lib/app/settings/settings.page.dart @@ -1,16 +1,21 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/app/categories/categories_list.dart'; +import 'package:monekin/app/currencies/currency_manager.dart'; +import 'package:monekin/app/settings/about_page.dart'; +import 'package:monekin/app/settings/appearance_settings_page.dart'; +import 'package:monekin/app/settings/backup_settings_page.dart'; import 'package:monekin/app/settings/edit_profile_modal.dart'; +import 'package:monekin/app/settings/help_us_page.dart'; +import 'package:monekin/app/tags/tag_list.page.dart'; import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; import 'package:monekin/core/presentation/widgets/skeleton.dart'; import 'package:monekin/core/presentation/widgets/user_avatar.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/utils/color_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import '../../core/presentation/app_colors.dart'; -@RoutePage() class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @@ -100,49 +105,54 @@ class _SettingsPageState extends State { title: t.general.categories, subtitle: t.settings.general.categories_descr, icon: Icons.category_rounded, - onTap: () => context.pushRoute(const CategoriesListRoute()), + onTap: () => + RouteUtils.pushRoute(context, const CategoriesListPage()), ), createSettingItem( context, title: t.tags.display(n: 10), subtitle: t.settings.general.categories_descr, icon: Icons.label_outline_rounded, - onTap: () => context.pushRoute(TagListRoute()), + onTap: () => RouteUtils.pushRoute(context, const TagListPage()), ), createSettingItem( context, title: t.currencies.currency_manager, subtitle: t.currencies.currency_manager_descr, icon: Icons.currency_exchange, - onTap: () => context.pushRoute(const CurrencyManagerRoute()), + onTap: () => + RouteUtils.pushRoute(context, const CurrencyManagerPage()), ), createSettingItem( context, title: t.settings.general.appearance, subtitle: t.settings.general.appearance_descr, icon: Icons.palette_outlined, - onTap: () => context.pushRoute(const AdvancedSettingsRoute()), + onTap: () => + RouteUtils.pushRoute(context, const AdvancedSettingsPage()), ), createSettingItem( context, title: t.settings.data.display, subtitle: t.settings.data.display_descr, icon: Icons.storage_rounded, - onTap: () => context.pushRoute(const BackupSettingsRoute()), + onTap: () => + RouteUtils.pushRoute(context, const BackupSettingsPage()), ), createSettingItem( context, title: t.settings.about_us.display, subtitle: t.settings.about_us.description, icon: Icons.info_outline_rounded, - onTap: () => context.pushRoute(const AboutRoute()), + onTap: () => RouteUtils.pushRoute(context, const AboutPage()), ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.all(16), child: InkWell( radius: 8, - onTap: () => context.pushRoute(const HelpUsRoute()), + onTap: () => + RouteUtils.pushRoute(context, const HelpUsPage()), child: Card( clipBehavior: Clip.hardEdge, margin: const EdgeInsets.all(0), diff --git a/lib/app/stats/stats_page.dart b/lib/app/stats/stats_page.dart index 61977775..fa2deb07 100644 --- a/lib/app/stats/stats_page.dart +++ b/lib/app/stats/stats_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/stats/widgets/balance_bar_chart.dart'; import 'package:monekin/app/stats/widgets/finance_health_details.dart'; @@ -19,7 +18,6 @@ import 'package:monekin/i18n/translations.g.dart'; import '../accounts/all_accounts_balance.dart'; -@RoutePage() class StatsPage extends StatefulWidget { const StatsPage({super.key, this.initialIndex = 0}); diff --git a/lib/app/stats/widgets/movements_distribution/category_stats_modal.dart b/lib/app/stats/widgets/movements_distribution/category_stats_modal.dart index 41d269fb..0253b643 100644 --- a/lib/app/stats/widgets/movements_distribution/category_stats_modal.dart +++ b/lib/app/stats/widgets/movements_distribution/category_stats_modal.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/stats/widgets/movements_distribution/chart_by_categories.dart'; +import 'package:monekin/app/transactions/transactions.page.dart'; import 'package:monekin/core/database/services/exchange-rate/exchange_rate_service.dart'; import 'package:monekin/core/models/category/category.dart'; import 'package:monekin/core/models/supported-icon/icon_displayer.dart'; @@ -9,7 +9,7 @@ import 'package:monekin/core/models/supported-icon/supported_icon.dart'; import 'package:monekin/core/presentation/widgets/animated_progress_bar.dart'; import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/utils/color_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -113,8 +113,9 @@ class CategoryStatsModal extends StatelessWidget { ], ), IconButton( - onPressed: () => context.pushRoute( - TransactionsRoute( + onPressed: () => RouteUtils.pushRoute( + context, + TransactionsPage( filters: filters.copyWith( categories: [categoryData.category.id], includeParentCategoriesInSearch: true, diff --git a/lib/app/tags/tag_form_page.dart b/lib/app/tags/tag_form_page.dart index 7dd148a8..a051f157 100644 --- a/lib/app/tags/tag_form_page.dart +++ b/lib/app/tags/tag_form_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:drift/drift.dart' as drift; import 'package:flutter/material.dart'; import 'package:monekin/core/database/app_db.dart'; @@ -13,7 +12,6 @@ import 'package:monekin/core/utils/text_field_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; import 'package:uuid/uuid.dart'; -@RoutePage() class TagFormPage extends StatefulWidget { const TagFormPage({super.key, this.tag}); diff --git a/lib/app/tags/tag_list.page.dart b/lib/app/tags/tag_list.page.dart index 8d508067..af5d2635 100644 --- a/lib/app/tags/tag_list.page.dart +++ b/lib/app/tags/tag_list.page.dart @@ -1,7 +1,7 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/accounts/all_accounts_page.dart'; +import 'package:monekin/app/tags/tag_form_page.dart'; import 'package:monekin/core/database/services/tags/tags_service.dart'; import 'package:monekin/core/models/tags/tag.dart'; import 'package:monekin/core/presentation/app_colors.dart'; @@ -11,7 +11,7 @@ import 'package:monekin/core/presentation/widgets/modal_container.dart'; import 'package:monekin/core/presentation/widgets/monekin_reorderable_list.dart'; import 'package:monekin/core/presentation/widgets/scrollable_with_bottom_gradient.dart'; import 'package:monekin/core/presentation/widgets/tappable.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; Future?> showTagListModal( @@ -39,7 +39,6 @@ Future?> showTagListModal( ); } -@RoutePage() class TagListPage extends StatefulWidget { const TagListPage({ super.key, @@ -91,7 +90,8 @@ class _TagListPageState extends State { final tag = tags.elementAt(index); return Tappable( - onTap: () => context.pushRoute(TagFormRoute(tag: tag)), + onTap: () => + RouteUtils.pushRoute(context, TagFormPage(tag: tag)), bgColor: AppColors.of(context).light, margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), borderRadius: 12, @@ -182,7 +182,7 @@ class _TagListPageState extends State { floatingActionButton: FloatingActionButton.extended( icon: const Icon(Icons.add_rounded), label: Text(t.tags.add), - onPressed: () => context.pushRoute(TagFormRoute()), + onPressed: () => RouteUtils.pushRoute(context, const TagFormPage()), ), body: buildList(), ); diff --git a/lib/app/transactions/form/transaction_form.page.dart b/lib/app/transactions/form/transaction_form.page.dart index fb749d06..abae0e4b 100644 --- a/lib/app/transactions/form/transaction_form.page.dart +++ b/lib/app/transactions/form/transaction_form.page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:drift/drift.dart' as drift; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -35,7 +34,6 @@ import '../../../core/presentation/app_colors.dart'; enum TransactionFormMode { transfer, incomeOrExpense } -@RoutePage() class TransactionFormPage extends StatefulWidget { const TransactionFormPage({ super.key, diff --git a/lib/app/transactions/form/widgets/interval_selector.dart b/lib/app/transactions/form/widgets/interval_selector.dart index 52e57b50..71b1a052 100644 --- a/lib/app/transactions/form/widgets/interval_selector.dart +++ b/lib/app/transactions/form/widgets/interval_selector.dart @@ -1,4 +1,4 @@ -import 'package:auto_route/auto_route.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; @@ -11,7 +11,6 @@ import 'package:monekin/core/presentation/widgets/persistent_footer_button.dart' import 'package:monekin/core/utils/text_field_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; -@RoutePage() class IntervalSelectorPage extends StatefulWidget { const IntervalSelectorPage({super.key, this.preselectedRecurrentRule}); diff --git a/lib/app/transactions/form/widgets/interval_selector_help.dart b/lib/app/transactions/form/widgets/interval_selector_help.dart index affdb255..78aa7320 100644 --- a/lib/app/transactions/form/widgets/interval_selector_help.dart +++ b/lib/app/transactions/form/widgets/interval_selector_help.dart @@ -1,10 +1,10 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/core/routes/route_utils.dart'; +import 'package:monekin/app/transactions/form/widgets/interval_selector.dart'; import 'package:monekin/core/models/date-utils/periodicity.dart'; import 'package:monekin/core/models/transaction/recurrency_data.dart'; import 'package:monekin/core/models/transaction/rule_recurrent_limit.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/i18n/translations.g.dart'; class IntervalSelectorHelp extends StatefulWidget { @@ -73,13 +73,12 @@ class _IntervalSelectorHelpState extends State { onChanged: (value) { Navigator.of(context, rootNavigator: true).pop(); - context - .pushRoute( - IntervalSelectorRoute( + RouteUtils.pushRoute( + context, + IntervalSelectorPage( preselectedRecurrentRule: widget.selectedRecurrentRule, ), - ) - .then((value) { + ).then((value) { if (value == null) return; widget.onRecurrentRuleSelected(value as RecurrencyData); diff --git a/lib/app/transactions/recurrent_transactions_page.dart b/lib/app/transactions/recurrent_transactions_page.dart index 638b8250..a4229725 100644 --- a/lib/app/transactions/recurrent_transactions_page.dart +++ b/lib/app/transactions/recurrent_transactions_page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/transactions/widgets/transaction_list.dart'; @@ -9,7 +8,6 @@ import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_ import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; import 'package:monekin/i18n/translations.g.dart'; -@RoutePage() class RecurrentTransactionPage extends StatefulWidget { const RecurrentTransactionPage({super.key}); @@ -52,6 +50,8 @@ class _RecurrentTransactionPageState extends State { prevPage: const RecurrentTransactionPage(), periodicityInfo: periodicity, showGroupDivider: false, + heroTagBuilder: (tr) => + 'recurrent-transactions-page__tr-icon-${tr.id}', onEmptyList: Center( child: EmptyIndicator( title: t.general.empty_warn, diff --git a/lib/app/transactions/transaction_details.page.dart b/lib/app/transactions/transaction_details.page.dart index 8b9da043..366c5dce 100644 --- a/lib/app/transactions/transaction_details.page.dart +++ b/lib/app/transactions/transaction_details.page.dart @@ -1,4 +1,3 @@ -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:drift/drift.dart' as drift; import 'package:flutter/material.dart'; @@ -36,16 +35,18 @@ class TransactionDetailAction { }); } -@RoutePage() class TransactionDetailsPage extends StatefulWidget { const TransactionDetailsPage({ super.key, required this.transaction, required this.prevPage, + required this.heroTag, }); final MoneyTransaction transaction; + final Object? heroTag; + /// Widget to navigate if the transaction is removed final Widget prevPage; @@ -550,7 +551,7 @@ class _TransactionDetailsPageState extends State { ), const SizedBox(width: 24), Hero( - tag: 'transaction-icon-${transaction.id}', + tag: widget.heroTag ?? UniqueKey(), child: transaction.isIncomeOrExpense ? IconDisplayer.fromCategory( context, diff --git a/lib/app/transactions/transactions.page.dart b/lib/app/transactions/transactions.page.dart index c7b7b23e..483b5519 100644 --- a/lib/app/transactions/transactions.page.dart +++ b/lib/app/transactions/transactions.page.dart @@ -1,6 +1,6 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:monekin/app/home/main_layout.dart'; +import 'package:monekin/app/layout/tabs.dart'; +import 'package:monekin/app/transactions/form/transaction_form.page.dart'; import 'package:monekin/app/transactions/widgets/transaction_list.dart'; import 'package:monekin/core/database/services/transaction/transaction_service.dart'; import 'package:monekin/core/presentation/widgets/empty_indicator.dart'; @@ -9,10 +9,9 @@ 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/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; -@RoutePage() class TransactionsPage extends StatefulWidget { const TransactionsPage({super.key, this.filters}); @@ -131,8 +130,9 @@ class _TransactionsPageState extends State { floatingActionButton: FloatingActionButton.extended( icon: const Icon(Icons.add_rounded), label: Text(t.transaction.create), - onPressed: () => context.pushRoute( - TransactionFormRoute(), + onPressed: () => RouteUtils.pushRoute( + context, + TransactionFormPage(), ), ), body: Column( @@ -187,8 +187,9 @@ class _TransactionsPageState extends State { ), Expanded( child: TransactionListComponent( + heroTagBuilder: (tr) => 'transactions-page__tr-icon-${tr.id}', filters: filters.copyWith(searchValue: searchValue), - prevPage: const MainLayoutPage(), + prevPage: const TabsPage(), onEmptyList: EmptyIndicator( title: t.general.empty_warn, description: t.transaction.list.empty), diff --git a/lib/app/transactions/widgets/transaction_list.dart b/lib/app/transactions/widgets/transaction_list.dart index 0bfdd169..a8388941 100644 --- a/lib/app/transactions/widgets/transaction_list.dart +++ b/lib/app/transactions/widgets/transaction_list.dart @@ -4,6 +4,7 @@ import 'package:monekin/app/transactions/widgets/transaction_list_tile.dart'; import 'package:monekin/core/database/services/account/account_service.dart'; import 'package:monekin/core/database/services/transaction/transaction_service.dart'; import 'package:monekin/core/models/date-utils/periodicity.dart'; +import 'package:monekin/core/models/transaction/transaction.dart'; import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; @@ -24,6 +25,7 @@ class TransactionListComponent extends StatefulWidget { ], ), required this.onEmptyList, + required this.heroTagBuilder, }); final TransactionFilters filters; @@ -44,6 +46,8 @@ class TransactionListComponent extends StatefulWidget { final Widget prevPage; + final Object? Function(MoneyTransaction tr)? heroTagBuilder; + @override State createState() => _TransactionListComponentState(); @@ -144,12 +148,17 @@ class _TransactionListComponentState extends State { final transaction = transactions[index - 1]; + final heroTag = widget.heroTagBuilder != null + ? widget.heroTagBuilder!(transaction) + : null; + return TransactionListTile( transaction: transaction, prevPage: widget.prevPage, periodicityInfo: widget.periodicityInfo, showDate: !widget.showGroupDivider, showTime: widget.showGroupDivider, + heroTag: heroTag, ); }, separatorBuilder: (context, index) { diff --git a/lib/app/transactions/widgets/transaction_list_tile.dart b/lib/app/transactions/widgets/transaction_list_tile.dart index 180bff3f..f54c3d6b 100644 --- a/lib/app/transactions/widgets/transaction_list_tile.dart +++ b/lib/app/transactions/widgets/transaction_list_tile.dart @@ -1,26 +1,28 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:monekin/core/routes/route_utils.dart'; +import 'package:monekin/app/transactions/transaction_details.page.dart'; import 'package:monekin/core/models/date-utils/periodicity.dart'; import 'package:monekin/core/models/supported-icon/icon_displayer.dart'; import 'package:monekin/core/models/transaction/transaction.dart'; import 'package:monekin/core/models/transaction/transaction_status.dart'; import 'package:monekin/core/presentation/widgets/number_ui_formatters/currency_displayer.dart'; import 'package:monekin/core/presentation/widgets/number_ui_formatters/ui_number_formatter.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/core/services/view-actions/transaction_view_actions_service.dart'; import 'package:monekin/core/utils/color_utils.dart'; import '../../../core/presentation/app_colors.dart'; class TransactionListTile extends StatelessWidget { - const TransactionListTile( - {super.key, - required this.transaction, - required this.prevPage, - this.showDate = true, - this.showTime = true, - this.periodicityInfo}); + const TransactionListTile({ + super.key, + required this.transaction, + required this.prevPage, + this.showDate = true, + this.showTime = true, + this.periodicityInfo, + required this.heroTag, + }); final MoneyTransaction transaction; @@ -29,6 +31,8 @@ class TransactionListTile extends StatelessWidget { final bool showDate; final bool showTime; + final Object? heroTag; + showTransactionActions(BuildContext context, MoneyTransaction transaction) { showModalBottomSheet( context: context, @@ -205,7 +209,7 @@ class TransactionListTile extends StatelessWidget { ), ), leading: Hero( - tag: 'transaction-icon-${transaction.id}', + tag: heroTag ?? UniqueKey(), child: transaction.isIncomeOrExpense ? IconDisplayer.fromCategory( context, @@ -221,8 +225,13 @@ class TransactionListTile extends StatelessWidget { ), ), onTap: () { - context.pushRoute( - TransactionDetailsRoute(transaction: transaction, prevPage: prevPage), + RouteUtils.pushRoute( + context, + TransactionDetailsPage( + transaction: transaction, + heroTag: heroTag, + prevPage: prevPage, + ), ); }, onLongPress: () => showTransactionActions(context, transaction), diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart deleted file mode 100644 index 33f4ddc3..00000000 --- a/lib/core/routes/app_router.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:monekin/app/accounts/account_form.dart'; -import 'package:monekin/app/accounts/all_accounts_page.dart'; -import 'package:monekin/app/accounts/details/account_details.dart'; -import 'package:monekin/app/budgets/budget_details_page.dart'; -import 'package:monekin/app/budgets/budget_form_page.dart'; -import 'package:monekin/app/budgets/budgets_page.dart'; -import 'package:monekin/app/categories/categories_list.dart'; -import 'package:monekin/app/categories/form/category_form.dart'; -import 'package:monekin/app/currencies/currency_manager.dart'; -import 'package:monekin/app/currencies/exchange_rate_details.dart'; -import 'package:monekin/app/home/dashboard.page.dart'; -import 'package:monekin/app/home/main_layout.dart'; -import 'package:monekin/app/onboarding/intro.page.dart'; -import 'package:monekin/app/onboarding/onboarding.dart'; -import 'package:monekin/app/settings/about_page.dart'; -import 'package:monekin/app/settings/appearance_settings_page.dart'; -import 'package:monekin/app/settings/backup_settings_page.dart'; -import 'package:monekin/app/settings/export_page.dart'; -import 'package:monekin/app/settings/help_us_page.dart'; -import 'package:monekin/app/settings/import_csv.dart'; -import 'package:monekin/app/settings/settings.page.dart'; -import 'package:monekin/app/stats/stats_page.dart'; -import 'package:monekin/app/tags/tag_form_page.dart'; -import 'package:monekin/app/tags/tag_list.page.dart'; -import 'package:monekin/app/transactions/form/transaction_form.page.dart'; -import 'package:monekin/app/transactions/form/widgets/interval_selector.dart'; -import 'package:monekin/app/transactions/recurrent_transactions_page.dart'; -import 'package:monekin/app/transactions/transaction_details.page.dart'; -import 'package:monekin/app/transactions/transactions.page.dart'; -import 'package:monekin/core/database/services/app-data/app_data_service.dart'; -import 'package:monekin/core/models/account/account.dart'; -import 'package:monekin/core/models/budget/budget.dart'; -import 'package:monekin/core/models/currency/currency.dart'; -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/presentation/widgets/transaction_filter/transaction_filters.dart'; - -part 'app_router.gr.dart'; - -class IntroSeenGuard extends AutoRouteGuard { - @override - void onNavigation(NavigationResolver resolver, StackRouter router) { - AppDataService.instance - .getAppDataItem(AppDataKey.introSeen) - .first - .then((value) { - if (value == null || value != '1') { - router.push(const IntroRoute()); - return; - } - - return resolver.next(true); - }); - } -} - -@AutoRouterConfig() -class AppRouter extends _$AppRouter { - final _routes = [ - AutoRoute(page: BudgetsRoute.page), - AutoRoute(page: BudgetDetailsRoute.page), - AutoRoute(page: BudgetFormRoute.page), - AutoRoute(page: AllAccountsRoute.page), - AutoRoute(page: TransactionsRoute.page), - AutoRoute(page: RecurrentTransactionRoute.page), - AutoRoute(page: AccountFormRoute.page), - AutoRoute(page: CurrencyManagerRoute.page), - AutoRoute(page: CategoriesListRoute.page), - AutoRoute(page: CategoryFormRoute.page), - AutoRoute(page: IntervalSelectorRoute.page), - AutoRoute(page: ExchangeRateDetailsRoute.page), - AutoRoute(page: TagFormRoute.page), - AutoRoute(page: TagListRoute.page), - AutoRoute(page: AccountDetailsRoute.page), - AutoRoute(page: TransactionFormRoute.page), - AutoRoute(page: TransactionDetailsRoute.page), - AutoRoute(page: StatsRoute.page), - AutoRoute(page: SettingsRoute.page), - AutoRoute(page: AboutRoute.page), - AutoRoute(page: HelpUsRoute.page), - AutoRoute(page: ImportCSVRoute.page), - AutoRoute(page: BackupSettingsRoute.page), - AutoRoute(page: AdvancedSettingsRoute.page), - AutoRoute(page: ExportDataRoute.page), - ]; - - @override - List get routes => [ - AutoRoute(page: IntroRoute.page), - AutoRoute(page: OnboardingRoute.page), - AutoRoute( - page: MainLayoutRoute.page, - initial: true, - guards: [IntroSeenGuard()], - children: [ - AutoRoute( - page: DashboardRoute.page, - initial: true, - ), - ..._routes, - ], - ), - ..._routes - ]; -} diff --git a/lib/core/routes/app_router.gr.dart b/lib/core/routes/app_router.gr.dart deleted file mode 100644 index 32959ef6..00000000 --- a/lib/core/routes/app_router.gr.dart +++ /dev/null @@ -1,1014 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// AutoRouterGenerator -// ************************************************************************** - -// ignore_for_file: type=lint -// coverage:ignore-file - -part of 'app_router.dart'; - -abstract class _$AppRouter extends RootStackRouter { - // ignore: unused_element - _$AppRouter({super.navigatorKey}); - - @override - final Map pagesMap = { - AboutRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const AboutPage(), - ); - }, - AccountDetailsRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: AccountDetailsPage( - key: args.key, - account: args.account, - ), - ); - }, - AccountFormRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const AccountFormRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: AccountFormPage( - key: args.key, - account: args.account, - ), - ); - }, - AdvancedSettingsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const AdvancedSettingsPage(), - ); - }, - AllAccountsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const AllAccountsPage(), - ); - }, - BackupSettingsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const BackupSettingsPage(), - ); - }, - BudgetDetailsRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: BudgetDetailsPage( - key: args.key, - budget: args.budget, - ), - ); - }, - BudgetFormRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: BudgetFormPage( - key: args.key, - budgetToEdit: args.budgetToEdit, - prevPage: args.prevPage, - ), - ); - }, - BudgetsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const BudgetsPage(), - ); - }, - CategoriesListRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: CategoriesListPage(), - ); - }, - CategoryFormRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const CategoryFormRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: CategoryFormPage( - key: args.key, - categoryUUID: args.categoryUUID, - ), - ); - }, - CurrencyManagerRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const CurrencyManagerPage(), - ); - }, - DashboardRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const DashboardPage(), - ); - }, - ExchangeRateDetailsRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: ExchangeRateDetailsPage( - key: args.key, - currency: args.currency, - ), - ); - }, - ExportDataRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const ExportDataPage(), - ); - }, - HelpUsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const HelpUsPage(), - ); - }, - ImportCSVRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const ImportCSVPage(), - ); - }, - IntervalSelectorRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const IntervalSelectorRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: IntervalSelectorPage( - key: args.key, - preselectedRecurrentRule: args.preselectedRecurrentRule, - ), - ); - }, - IntroRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const IntroPage(), - ); - }, - MainLayoutRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const MainLayoutPage(), - ); - }, - OnboardingRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const OnboardingPage(), - ); - }, - RecurrentTransactionRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const RecurrentTransactionPage(), - ); - }, - SettingsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const SettingsPage(), - ); - }, - StatsRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const StatsRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: StatsPage( - key: args.key, - initialIndex: args.initialIndex, - ), - ); - }, - TagFormRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const TagFormRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: TagFormPage( - key: args.key, - tag: args.tag, - ), - ); - }, - TagListRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const TagListRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: TagListPage( - key: args.key, - isModal: args.isModal, - selected: args.selected, - scrollController: args.scrollController, - ), - ); - }, - TransactionDetailsRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: TransactionDetailsPage( - key: args.key, - transaction: args.transaction, - prevPage: args.prevPage, - ), - ); - }, - TransactionFormRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const TransactionFormRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: TransactionFormPage( - key: args.key, - transactionToEdit: args.transactionToEdit, - mode: args.mode, - fromAccount: args.fromAccount, - toAccount: args.toAccount, - ), - ); - }, - TransactionsRoute.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const TransactionsRouteArgs()); - return AutoRoutePage( - routeData: routeData, - child: TransactionsPage( - key: args.key, - filters: args.filters, - ), - ); - }, - }; -} - -/// generated route for -/// [AboutPage] -class AboutRoute extends PageRouteInfo { - const AboutRoute({List? children}) - : super( - AboutRoute.name, - initialChildren: children, - ); - - static const String name = 'AboutRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [AccountDetailsPage] -class AccountDetailsRoute extends PageRouteInfo { - AccountDetailsRoute({ - Key? key, - required Account account, - List? children, - }) : super( - AccountDetailsRoute.name, - args: AccountDetailsRouteArgs( - key: key, - account: account, - ), - initialChildren: children, - ); - - static const String name = 'AccountDetailsRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class AccountDetailsRouteArgs { - const AccountDetailsRouteArgs({ - this.key, - required this.account, - }); - - final Key? key; - - final Account account; - - @override - String toString() { - return 'AccountDetailsRouteArgs{key: $key, account: $account}'; - } -} - -/// generated route for -/// [AccountFormPage] -class AccountFormRoute extends PageRouteInfo { - AccountFormRoute({ - Key? key, - Account? account, - List? children, - }) : super( - AccountFormRoute.name, - args: AccountFormRouteArgs( - key: key, - account: account, - ), - initialChildren: children, - ); - - static const String name = 'AccountFormRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class AccountFormRouteArgs { - const AccountFormRouteArgs({ - this.key, - this.account, - }); - - final Key? key; - - final Account? account; - - @override - String toString() { - return 'AccountFormRouteArgs{key: $key, account: $account}'; - } -} - -/// generated route for -/// [AdvancedSettingsPage] -class AdvancedSettingsRoute extends PageRouteInfo { - const AdvancedSettingsRoute({List? children}) - : super( - AdvancedSettingsRoute.name, - initialChildren: children, - ); - - static const String name = 'AdvancedSettingsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [AllAccountsPage] -class AllAccountsRoute extends PageRouteInfo { - const AllAccountsRoute({List? children}) - : super( - AllAccountsRoute.name, - initialChildren: children, - ); - - static const String name = 'AllAccountsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [BackupSettingsPage] -class BackupSettingsRoute extends PageRouteInfo { - const BackupSettingsRoute({List? children}) - : super( - BackupSettingsRoute.name, - initialChildren: children, - ); - - static const String name = 'BackupSettingsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [BudgetDetailsPage] -class BudgetDetailsRoute extends PageRouteInfo { - BudgetDetailsRoute({ - Key? key, - required Budget budget, - List? children, - }) : super( - BudgetDetailsRoute.name, - args: BudgetDetailsRouteArgs( - key: key, - budget: budget, - ), - initialChildren: children, - ); - - static const String name = 'BudgetDetailsRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class BudgetDetailsRouteArgs { - const BudgetDetailsRouteArgs({ - this.key, - required this.budget, - }); - - final Key? key; - - final Budget budget; - - @override - String toString() { - return 'BudgetDetailsRouteArgs{key: $key, budget: $budget}'; - } -} - -/// generated route for -/// [BudgetFormPage] -class BudgetFormRoute extends PageRouteInfo { - BudgetFormRoute({ - Key? key, - Budget? budgetToEdit, - required Widget prevPage, - List? children, - }) : super( - BudgetFormRoute.name, - args: BudgetFormRouteArgs( - key: key, - budgetToEdit: budgetToEdit, - prevPage: prevPage, - ), - initialChildren: children, - ); - - static const String name = 'BudgetFormRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class BudgetFormRouteArgs { - const BudgetFormRouteArgs({ - this.key, - this.budgetToEdit, - required this.prevPage, - }); - - final Key? key; - - final Budget? budgetToEdit; - - final Widget prevPage; - - @override - String toString() { - return 'BudgetFormRouteArgs{key: $key, budgetToEdit: $budgetToEdit, prevPage: $prevPage}'; - } -} - -/// generated route for -/// [BudgetsPage] -class BudgetsRoute extends PageRouteInfo { - const BudgetsRoute({List? children}) - : super( - BudgetsRoute.name, - initialChildren: children, - ); - - static const String name = 'BudgetsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [CategoriesListPage] -class CategoriesListRoute extends PageRouteInfo { - const CategoriesListRoute({List? children}) - : super( - CategoriesListRoute.name, - initialChildren: children, - ); - - static const String name = 'CategoriesListRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [CategoryFormPage] -class CategoryFormRoute extends PageRouteInfo { - CategoryFormRoute({ - Key? key, - String? categoryUUID, - List? children, - }) : super( - CategoryFormRoute.name, - args: CategoryFormRouteArgs( - key: key, - categoryUUID: categoryUUID, - ), - initialChildren: children, - ); - - static const String name = 'CategoryFormRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class CategoryFormRouteArgs { - const CategoryFormRouteArgs({ - this.key, - this.categoryUUID, - }); - - final Key? key; - - final String? categoryUUID; - - @override - String toString() { - return 'CategoryFormRouteArgs{key: $key, categoryUUID: $categoryUUID}'; - } -} - -/// generated route for -/// [CurrencyManagerPage] -class CurrencyManagerRoute extends PageRouteInfo { - const CurrencyManagerRoute({List? children}) - : super( - CurrencyManagerRoute.name, - initialChildren: children, - ); - - static const String name = 'CurrencyManagerRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [DashboardPage] -class DashboardRoute extends PageRouteInfo { - const DashboardRoute({List? children}) - : super( - DashboardRoute.name, - initialChildren: children, - ); - - static const String name = 'DashboardRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [ExchangeRateDetailsPage] -class ExchangeRateDetailsRoute - extends PageRouteInfo { - ExchangeRateDetailsRoute({ - Key? key, - required Currency currency, - List? children, - }) : super( - ExchangeRateDetailsRoute.name, - args: ExchangeRateDetailsRouteArgs( - key: key, - currency: currency, - ), - initialChildren: children, - ); - - static const String name = 'ExchangeRateDetailsRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class ExchangeRateDetailsRouteArgs { - const ExchangeRateDetailsRouteArgs({ - this.key, - required this.currency, - }); - - final Key? key; - - final Currency currency; - - @override - String toString() { - return 'ExchangeRateDetailsRouteArgs{key: $key, currency: $currency}'; - } -} - -/// generated route for -/// [ExportDataPage] -class ExportDataRoute extends PageRouteInfo { - const ExportDataRoute({List? children}) - : super( - ExportDataRoute.name, - initialChildren: children, - ); - - static const String name = 'ExportDataRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [HelpUsPage] -class HelpUsRoute extends PageRouteInfo { - const HelpUsRoute({List? children}) - : super( - HelpUsRoute.name, - initialChildren: children, - ); - - static const String name = 'HelpUsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [ImportCSVPage] -class ImportCSVRoute extends PageRouteInfo { - const ImportCSVRoute({List? children}) - : super( - ImportCSVRoute.name, - initialChildren: children, - ); - - static const String name = 'ImportCSVRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [IntervalSelectorPage] -class IntervalSelectorRoute extends PageRouteInfo { - IntervalSelectorRoute({ - Key? key, - RecurrencyData? preselectedRecurrentRule, - List? children, - }) : super( - IntervalSelectorRoute.name, - args: IntervalSelectorRouteArgs( - key: key, - preselectedRecurrentRule: preselectedRecurrentRule, - ), - initialChildren: children, - ); - - static const String name = 'IntervalSelectorRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class IntervalSelectorRouteArgs { - const IntervalSelectorRouteArgs({ - this.key, - this.preselectedRecurrentRule, - }); - - final Key? key; - - final RecurrencyData? preselectedRecurrentRule; - - @override - String toString() { - return 'IntervalSelectorRouteArgs{key: $key, preselectedRecurrentRule: $preselectedRecurrentRule}'; - } -} - -/// generated route for -/// [IntroPage] -class IntroRoute extends PageRouteInfo { - const IntroRoute({List? children}) - : super( - IntroRoute.name, - initialChildren: children, - ); - - static const String name = 'IntroRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [MainLayoutPage] -class MainLayoutRoute extends PageRouteInfo { - const MainLayoutRoute({List? children}) - : super( - MainLayoutRoute.name, - initialChildren: children, - ); - - static const String name = 'MainLayoutRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [OnboardingPage] -class OnboardingRoute extends PageRouteInfo { - const OnboardingRoute({List? children}) - : super( - OnboardingRoute.name, - initialChildren: children, - ); - - static const String name = 'OnboardingRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [RecurrentTransactionPage] -class RecurrentTransactionRoute extends PageRouteInfo { - const RecurrentTransactionRoute({List? children}) - : super( - RecurrentTransactionRoute.name, - initialChildren: children, - ); - - static const String name = 'RecurrentTransactionRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [SettingsPage] -class SettingsRoute extends PageRouteInfo { - const SettingsRoute({List? children}) - : super( - SettingsRoute.name, - initialChildren: children, - ); - - static const String name = 'SettingsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [StatsPage] -class StatsRoute extends PageRouteInfo { - StatsRoute({ - Key? key, - int initialIndex = 0, - List? children, - }) : super( - StatsRoute.name, - args: StatsRouteArgs( - key: key, - initialIndex: initialIndex, - ), - initialChildren: children, - ); - - static const String name = 'StatsRoute'; - - static const PageInfo page = PageInfo(name); -} - -class StatsRouteArgs { - const StatsRouteArgs({ - this.key, - this.initialIndex = 0, - }); - - final Key? key; - - final int initialIndex; - - @override - String toString() { - return 'StatsRouteArgs{key: $key, initialIndex: $initialIndex}'; - } -} - -/// generated route for -/// [TagFormPage] -class TagFormRoute extends PageRouteInfo { - TagFormRoute({ - Key? key, - Tag? tag, - List? children, - }) : super( - TagFormRoute.name, - args: TagFormRouteArgs( - key: key, - tag: tag, - ), - initialChildren: children, - ); - - static const String name = 'TagFormRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class TagFormRouteArgs { - const TagFormRouteArgs({ - this.key, - this.tag, - }); - - final Key? key; - - final Tag? tag; - - @override - String toString() { - return 'TagFormRouteArgs{key: $key, tag: $tag}'; - } -} - -/// generated route for -/// [TagListPage] -class TagListRoute extends PageRouteInfo { - TagListRoute({ - Key? key, - bool isModal = false, - List selected = const [], - ScrollController? scrollController, - List? children, - }) : super( - TagListRoute.name, - args: TagListRouteArgs( - key: key, - isModal: isModal, - selected: selected, - scrollController: scrollController, - ), - initialChildren: children, - ); - - static const String name = 'TagListRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class TagListRouteArgs { - const TagListRouteArgs({ - this.key, - this.isModal = false, - this.selected = const [], - this.scrollController, - }); - - final Key? key; - - final bool isModal; - - final List selected; - - final ScrollController? scrollController; - - @override - String toString() { - return 'TagListRouteArgs{key: $key, isModal: $isModal, selected: $selected, scrollController: $scrollController}'; - } -} - -/// generated route for -/// [TransactionDetailsPage] -class TransactionDetailsRoute - extends PageRouteInfo { - TransactionDetailsRoute({ - Key? key, - required MoneyTransaction transaction, - required Widget prevPage, - List? children, - }) : super( - TransactionDetailsRoute.name, - args: TransactionDetailsRouteArgs( - key: key, - transaction: transaction, - prevPage: prevPage, - ), - initialChildren: children, - ); - - static const String name = 'TransactionDetailsRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class TransactionDetailsRouteArgs { - const TransactionDetailsRouteArgs({ - this.key, - required this.transaction, - required this.prevPage, - }); - - final Key? key; - - final MoneyTransaction transaction; - - final Widget prevPage; - - @override - String toString() { - return 'TransactionDetailsRouteArgs{key: $key, transaction: $transaction, prevPage: $prevPage}'; - } -} - -/// generated route for -/// [TransactionFormPage] -class TransactionFormRoute extends PageRouteInfo { - TransactionFormRoute({ - Key? key, - MoneyTransaction? transactionToEdit, - TransactionFormMode mode = TransactionFormMode.incomeOrExpense, - Account? fromAccount, - Account? toAccount, - List? children, - }) : super( - TransactionFormRoute.name, - args: TransactionFormRouteArgs( - key: key, - transactionToEdit: transactionToEdit, - mode: mode, - fromAccount: fromAccount, - toAccount: toAccount, - ), - initialChildren: children, - ); - - static const String name = 'TransactionFormRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class TransactionFormRouteArgs { - const TransactionFormRouteArgs({ - this.key, - this.transactionToEdit, - this.mode = TransactionFormMode.incomeOrExpense, - this.fromAccount, - this.toAccount, - }); - - final Key? key; - - final MoneyTransaction? transactionToEdit; - - final TransactionFormMode mode; - - final Account? fromAccount; - - final Account? toAccount; - - @override - String toString() { - return 'TransactionFormRouteArgs{key: $key, transactionToEdit: $transactionToEdit, mode: $mode, fromAccount: $fromAccount, toAccount: $toAccount}'; - } -} - -/// generated route for -/// [TransactionsPage] -class TransactionsRoute extends PageRouteInfo { - TransactionsRoute({ - Key? key, - TransactionFilters? filters, - List? children, - }) : super( - TransactionsRoute.name, - args: TransactionsRouteArgs( - key: key, - filters: filters, - ), - initialChildren: children, - ); - - static const String name = 'TransactionsRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class TransactionsRouteArgs { - const TransactionsRouteArgs({ - this.key, - this.filters, - }); - - final Key? key; - - final TransactionFilters? filters; - - @override - String toString() { - return 'TransactionsRouteArgs{key: $key, filters: $filters}'; - } -} diff --git a/lib/core/routes/destinations.dart b/lib/core/routes/destinations.dart index ce2033e6..2ccaa8cc 100644 --- a/lib/core/routes/destinations.dart +++ b/lib/core/routes/destinations.dart @@ -1,22 +1,40 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:monekin/app/accounts/all_accounts_page.dart'; +import 'package:monekin/app/budgets/budgets_page.dart'; +import 'package:monekin/app/home/dashboard.page.dart'; +import 'package:monekin/app/settings/settings.page.dart'; +import 'package:monekin/app/stats/stats_page.dart'; +import 'package:monekin/app/transactions/recurrent_transactions_page.dart'; +import 'package:monekin/app/transactions/transactions.page.dart'; import 'package:monekin/core/presentation/responsive/breakpoints.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/i18n/translations.g.dart'; +enum AppMenuDestinationsID { + dashboard, + budgets, + transactions, + recurrentTransactions, + accounts, + stats, + settings, + categories, +} + class MainMenuDestination { - const MainMenuDestination({ + const MainMenuDestination( + this.id, { required this.destination, required this.label, required this.icon, this.selectedIcon, }); + final AppMenuDestinationsID id; final String label; final IconData icon; final IconData? selectedIcon; - final PageRouteInfo destination; + final Widget destination; NavigationDestination toNavigationDestinationWidget() { return NavigationDestination( @@ -38,68 +56,99 @@ class MainMenuDestination { return NavigationRailDestination( icon: Icon(icon), selectedIcon: Icon(selectedIcon ?? icon), - label: Text(label), + label: Text( + label, + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + softWrap: false, + ), ); } } -List getDestinations( +List getAllDestinations( BuildContext context, { required bool shortLabels, - bool showHome = true, }) { final t = Translations.of(context); - final bool isMobileMode = - BreakPoint.of(context).isSmallerThan(BreakpointID.md); - return [ - if (showHome || isMobileMode) - MainMenuDestination( - label: t.home.title, - icon: Icons.home_outlined, - selectedIcon: Icons.home, - destination: const DashboardRoute(), - ), - if (!isMobileMode) ...[ - MainMenuDestination( - label: t.budgets.title, - icon: Icons.calculate_outlined, - selectedIcon: Icons.calculate, - destination: const BudgetsRoute(), - ), - MainMenuDestination( - label: t.general.accounts, - icon: Icons.account_balance_rounded, - destination: const AllAccountsRoute(), - ), - ], - if (MediaQuery.of(context).size.height > 600 || isMobileMode) - MainMenuDestination( - label: t.transaction.display(n: 10), - icon: Icons.app_registration_rounded, - destination: TransactionsRoute(), - ), - if (!isMobileMode) ...[ + MainMenuDestination( + AppMenuDestinationsID.dashboard, + label: t.home.title, + icon: Icons.home_outlined, + selectedIcon: Icons.home, + destination: const DashboardPage(), + ), + MainMenuDestination( + AppMenuDestinationsID.budgets, + label: t.budgets.title, + icon: Icons.calculate_outlined, + selectedIcon: Icons.calculate, + destination: const BudgetsPage(), + ), + MainMenuDestination( + AppMenuDestinationsID.accounts, + label: t.general.accounts, + icon: Icons.account_balance_rounded, + destination: const AllAccountsPage(), + ), + MainMenuDestination( + AppMenuDestinationsID.transactions, + label: t.transaction.display(n: 10), + icon: Icons.app_registration_rounded, + destination: TransactionsPage(), + ), + MainMenuDestination( + AppMenuDestinationsID.recurrentTransactions, + label: shortLabels + ? t.recurrent_transactions.title_short + : t.recurrent_transactions.title, + icon: Icons.auto_mode_rounded, + destination: RecurrentTransactionPage(), + ), + if (MediaQuery.of(context).size.height > 650) MainMenuDestination( - label: shortLabels - ? t.recurrent_transactions.title_short - : t.recurrent_transactions.title, - icon: Icons.auto_mode_rounded, - destination: const RecurrentTransactionRoute(), + AppMenuDestinationsID.stats, + label: t.stats.title, + icon: Icons.auto_graph_rounded, + destination: StatsPage(), ), - if (MediaQuery.of(context).size.height > 650) - MainMenuDestination( - label: t.stats.title, - icon: Icons.auto_graph_rounded, - destination: StatsRoute(), - ), - ], MainMenuDestination( + AppMenuDestinationsID.settings, label: t.settings.title, selectedIcon: Icons.settings, icon: Icons.settings_outlined, - destination: const SettingsRoute(), + destination: const SettingsPage(), ), ]; } + +List getDestinations( + BuildContext context, { + required bool shortLabels, + bool showHome = true, +}) { + final bool isMobileMode = + BreakPoint.of(context).isSmallerThan(BreakpointID.md); + + var toReturn = getAllDestinations(context, shortLabels: shortLabels); + + if (!showHome) { + toReturn = toReturn + .where((element) => element.id != AppMenuDestinationsID.dashboard) + .toList(); + } + + if (isMobileMode) { + toReturn = toReturn + .where((element) => [ + AppMenuDestinationsID.dashboard, + AppMenuDestinationsID.transactions, + AppMenuDestinationsID.settings, + ].contains(element.id)) + .toList(); + } + + return toReturn; +} diff --git a/lib/core/routes/route_utils.dart b/lib/core/routes/route_utils.dart new file mode 100644 index 00000000..57110422 --- /dev/null +++ b/lib/core/routes/route_utils.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:monekin/main.dart'; + +abstract class RouteUtils { + static Future pushRoute( + BuildContext context, + Widget page, { + bool withReplacement = false, + }) { + if (navigatorKey.currentState == null) return Future.value(null); + + var pageRouteBuilder = PageRouteBuilder( + opaque: false, + transitionDuration: const Duration(milliseconds: 300), + reverseTransitionDuration: const Duration(milliseconds: 125), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + final tween = Tween( + begin: const Offset(0, 0.05), + end: Offset.zero, + ).chain( + CurveTween(curve: Curves.easeOut), + ); + + return SlideTransition( + position: animation.drive(tween), + child: FadeTransition(opacity: animation, child: child), + ); + }, + pageBuilder: (context, animation, secondaryAnimation) { + return page; + }, + ); + + if (withReplacement) { + return navigatorKey.currentState!.pushReplacement(pageRouteBuilder); + } + + return navigatorKey.currentState!.push(pageRouteBuilder); + } +} diff --git a/lib/core/services/view-actions/transaction_view_actions_service.dart b/lib/core/services/view-actions/transaction_view_actions_service.dart index 64335c47..f72aa413 100644 --- a/lib/core/services/view-actions/transaction_view_actions_service.dart +++ b/lib/core/services/view-actions/transaction_view_actions_service.dart @@ -1,11 +1,10 @@ -import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:monekin/app/transactions/form/transaction_form.page.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/services/transaction/transaction_service.dart'; import 'package:monekin/core/models/transaction/transaction.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; -import 'package:monekin/core/routes/app_router.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/utils/list_tile_action_item.dart'; import 'package:uuid/uuid.dart'; @@ -27,12 +26,14 @@ class TransactionViewActionService { ListTileActionItem( label: t.general.edit, icon: Icons.edit, - onClick: () => context.pushRoute(TransactionFormRoute( - transactionToEdit: transaction, - mode: transaction.isIncomeOrExpense - ? TransactionFormMode.incomeOrExpense - : TransactionFormMode.transfer, - )), + onClick: () => RouteUtils.pushRoute( + context, + TransactionFormPage( + transactionToEdit: transaction, + mode: transaction.isIncomeOrExpense + ? TransactionFormMode.incomeOrExpense + : TransactionFormMode.transfer, + )), ), if (transaction.recurrentInfo.isNoRecurrent) ListTileActionItem( diff --git a/lib/i18n/translations.g.dart b/lib/i18n/translations.g.dart index fbf1178c..b6fb8dba 100644 --- a/lib/i18n/translations.g.dart +++ b/lib/i18n/translations.g.dart @@ -6,7 +6,7 @@ /// Locales: 2 /// Strings: 1026 (513 per locale) /// -/// Built on 2024-02-12 at 15:37 UTC +/// Built on 2024-02-13 at 15:34 UTC // coverage:ignore-file // ignore_for_file: type=lint diff --git a/lib/main.dart b/lib/main.dart index 42c12b20..36d3df7e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,9 +3,13 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:intl/intl.dart'; +import 'package:monekin/app/home/root_navigator_observer.dart'; +import 'package:monekin/app/layout/navigation_sidebar.dart'; +import 'package:monekin/app/layout/tabs.dart'; +import 'package:monekin/app/onboarding/intro.page.dart'; +import 'package:monekin/core/database/services/app-data/app_data_service.dart'; import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; import 'package:monekin/core/presentation/theme.dart'; -import 'package:monekin/core/routes/app_router.dart'; import 'package:monekin/core/utils/scroll_behavior_override.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -15,6 +19,10 @@ void main() { runApp(const MonekinAppEntryPoint()); } +final GlobalKey tabsPageKey = GlobalKey(); +final GlobalKey navigationSidebarKey = GlobalKey(); +final GlobalKey navigatorKey = GlobalKey(); + class MonekinAppEntryPoint extends StatelessWidget { const MonekinAppEntryPoint({ super.key, @@ -65,39 +73,53 @@ class MonekinAppEntryPoint extends StatelessWidget { } return TranslationProvider( - child: MaterialAppContainer( - amoledMode: userSettings - .firstWhere((element) => - element.settingKey == SettingKey.amoledMode) - .settingValue! == - '1', - accentColor: userSettings - .firstWhere( - (element) => element.settingKey == SettingKey.accentColor) - .settingValue!, - themeMode: ThemeMode.values.byName(userSettings - .firstWhere( - (element) => element.settingKey == SettingKey.themeMode) - .settingValue!), - ), + child: StreamBuilder( + stream: AppDataService.instance + .getAppDataItem(AppDataKey.introSeen) + .map((event) => event == '1'), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Container(); + } + + return MaterialAppContainer( + introSeen: snapshot.data!, + amoledMode: userSettings + .firstWhere((element) => + element.settingKey == SettingKey.amoledMode) + .settingValue! == + '1', + accentColor: userSettings + .firstWhere((element) => + element.settingKey == SettingKey.accentColor) + .settingValue!, + themeMode: ThemeMode.values.byName(userSettings + .firstWhere((element) => + element.settingKey == SettingKey.themeMode) + .settingValue!), + ); + }), ); }); } } -final _appRouter = AppRouter(); +int refresh = 1; class MaterialAppContainer extends StatelessWidget { const MaterialAppContainer( {super.key, required this.themeMode, required this.accentColor, - required this.amoledMode}); + required this.amoledMode, + required this.introSeen}); final ThemeMode themeMode; final String accentColor; final bool amoledMode; + final bool introSeen; + @override Widget build(BuildContext context) { // Get the language of the Intl in each rebuild of the TranslationProvider: @@ -105,8 +127,9 @@ class MaterialAppContainer extends StatelessWidget { return DynamicColorBuilder( builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { - return MaterialApp.router( + return MaterialApp( title: 'Monekin', + key: ValueKey(refresh), debugShowCheckedModeBanner: false, locale: TranslationProvider.of(context).flutterLocale, scrollBehavior: ScrollBehaviorOverride(), @@ -125,7 +148,32 @@ class MaterialAppContainer extends StatelessWidget { darkDynamic: darkDynamic, accentColor: accentColor), themeMode: themeMode, - routerConfig: _appRouter.config(), + navigatorKey: navigatorKey, + navigatorObservers: [MainLayoutNavObserver()], + builder: (context, child) { + return Overlay(initialEntries: [ + OverlayEntry( + builder: (context) => Stack( + children: [ + Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 1500), + curve: Curves.easeInOutCubicEmphasized, + width: + introSeen ? getNavigationSidebarWidth(context) : 0, + color: Theme.of(context).canvasColor, + ), + Expanded(child: child ?? const SizedBox.shrink()), + ], + ), + if (introSeen) NavigationSidebar(key: navigationSidebarKey) + ], + ), + ), + ]); + }, + home: introSeen ? TabsPage(key: tabsPageKey) : IntroPage(), ); }); } diff --git a/pubspec.lock b/pubspec.lock index 34fde654..5ac1968d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,22 +49,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" - auto_route: - dependency: "direct main" - description: - name: auto_route - sha256: "82f8df1d177416bc6b7a449127d0270ff1f0f633a91f2ceb7a85d4f07c3affa1" - url: "https://pub.dev" - source: hosted - version: "7.8.4" - auto_route_generator: - dependency: "direct dev" - description: - name: auto_route_generator - sha256: "11067a3bcd643812518fe26c0c9ec073990286cabfd9d74b6da9ef9b913c4d22" - url: "https://pub.dev" - source: hosted - version: "7.3.2" boolean_selector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index aaab2881..4ce0c189 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,6 @@ dependencies: copy_with_extension: ^5.0.4 freezed_annotation: ^2.4.1 in_app_purchase: ^3.1.11 - auto_route: ^7.8.4 dev_dependencies: flutter_test: @@ -78,7 +77,6 @@ dev_dependencies: slang_build_runner: ^3.18.0 # To regenerate the translations run 'dart run slang' copy_with_extension_gen: ^5.0.4 freezed: ^2.4.5 - auto_route_generator: ^7.3.2 flutter_launcher_icons: image_path: 'assets/resources/appIcon-removebg.png' From 69f787db561f779510893c030dd8a3eb9a635467 Mon Sep 17 00:00:00 2001 From: enriqueloz88 Date: Thu, 15 Feb 2024 12:26:26 +0100 Subject: [PATCH 4/5] feat: Change the settings page design --- lib/app/home/dashboard.page.dart | 104 +++-- lib/app/settings/about_page.dart | 20 +- .../settings/appearance_settings_page.dart | 33 +- lib/app/settings/backup_settings_page.dart | 4 +- lib/app/settings/help_us_page.dart | 60 ++- lib/app/settings/purchases/donate_button.dart | 10 +- .../settings/purchases/in_app_purchase.dart | 12 +- lib/app/settings/settings.page.dart | 296 ++++++++----- lib/core/routes/destinations.dart | 31 +- lib/i18n/strings_en.json | 45 +- lib/i18n/strings_es.json | 52 +-- lib/i18n/translations.g.dart | 418 +++++++++--------- 12 files changed, 583 insertions(+), 502 deletions(-) diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index a1f1de59..156227a9 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -1,10 +1,10 @@ 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/routes/route_utils.dart'; import 'package:monekin/app/home/widgets/home_drawer.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/finance_health/finance_health_main_info.dart'; @@ -26,6 +26,7 @@ import 'package:monekin/core/presentation/widgets/transaction_filter/transaction import 'package:monekin/core/presentation/widgets/trending_value.dart'; import 'package:monekin/core/presentation/widgets/user_avatar.dart'; import 'package:monekin/core/routes/destinations.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/services/finance_health_service.dart'; import 'package:monekin/core/utils/color_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -100,44 +101,70 @@ class _DashboardPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - StreamBuilder( - stream: UserSettingService.instance - .getSetting(SettingKey.avatar), - builder: (context, snapshot) { - return UserAvatar(avatar: snapshot.data); - }), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + 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: [ - Text( - "Welcome again!", - style: - Theme.of(context).textTheme.labelSmall!, - ), - 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 - .titleMedium! - .copyWith( - fontWeight: FontWeight.bold), - ); - }), + 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, + children: [ + Text( + "Welcome again!", + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontWeight: FontWeight.w300, + ), + ), + 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 + .titleLarge! + .copyWith( + // fontWeight: FontWeight.bold, + ), + ); + }), + ], + ) ], - ) - ], + ), + ), ), ActionChip( label: Text(dateRangeService.getText(context)), @@ -170,7 +197,8 @@ class _DashboardPageState extends State { ), ], ), - const Divider(height: 24), + const Divider(height: 16), + const SizedBox(height: 8), StreamBuilder( stream: AccountService.instance.getAccounts(), builder: (context, accounts) { diff --git a/lib/app/settings/about_page.dart b/lib/app/settings/about_page.dart index 74e14c5b..5db3d49a 100644 --- a/lib/app/settings/about_page.dart +++ b/lib/app/settings/about_page.dart @@ -33,7 +33,7 @@ class AboutPage extends StatelessWidget { final t = Translations.of(context); return Scaffold( - appBar: AppBar(title: Text(t.settings.about_us.display)), + appBar: AppBar(title: Text(t.more.about_us.display)), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -94,42 +94,42 @@ class AboutPage extends StatelessWidget { ), ), ), - createListSeparator(context, t.settings.about_us.project.display), + createListSeparator(context, t.more.about_us.project.display), buildLinkItem( - t.settings.about_us.project.contributors, - subtitle: t.settings.about_us.project.contributors_descr, + t.more.about_us.project.contributors, + subtitle: t.more.about_us.project.contributors_descr, onTap: () { openExternalURL(context, 'https://github.com/enrique-lozano/Monekin/graphs/contributors'); }, ), buildLinkItem( - t.settings.help_us.report, + t.more.help_us.report, onTap: () { openExternalURL(context, 'https://github.com/enrique-lozano/Monekin/issues/new/choose'); }, ), - buildLinkItem(t.settings.about_us.project.contact, onTap: () { + buildLinkItem(t.more.about_us.project.contact, onTap: () { openExternalURL(context, 'mailto:lozin.technologies@gmail.com'); }), - createListSeparator(context, t.settings.about_us.legal.display), + createListSeparator(context, t.more.about_us.legal.display), buildLinkItem( - t.settings.about_us.legal.terms, + t.more.about_us.legal.terms, onTap: () { openExternalURL(context, 'https://github.com/enrique-lozano/Monekin/blob/main/docs/TERMS_OF_USE.md'); }, ), buildLinkItem( - t.settings.about_us.legal.privacy, + t.more.about_us.legal.privacy, onTap: () { openExternalURL(context, 'https://github.com/enrique-lozano/Monekin/blob/main/docs/PRIVACY_POLICY.md'); }, ), buildLinkItem( - t.settings.about_us.legal.licenses, + t.more.about_us.legal.licenses, onTap: () async { openLicense({String? appName, String? version}) { showLicensePage( diff --git a/lib/app/settings/appearance_settings_page.dart b/lib/app/settings/appearance_settings_page.dart index e3601b46..4c9335c1 100644 --- a/lib/app/settings/appearance_settings_page.dart +++ b/lib/app/settings/appearance_settings_page.dart @@ -99,15 +99,15 @@ class _AdvancedSettingsPageState extends State { return Scaffold( appBar: AppBar( - title: Text(t.settings.general.appearance), + title: Text(t.settings.title_short), ), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - createListSeparator(context, t.settings.lang), + createListSeparator(context, t.settings.lang_section), buildSelector( - title: t.settings.lang, + title: t.settings.lang_title, dialogDescr: t.settings.lang_descr, items: [ SelectItem(value: 'es', label: t.lang.es), @@ -122,22 +122,17 @@ class _AdvancedSettingsPageState extends State { .then((value) => null); }, ), - createListSeparator(context, t.settings.general.app_colors), + createListSeparator(context, t.settings.theme_and_colors), StreamBuilder( stream: UserSettingService.instance .getSetting(SettingKey.themeMode), builder: (context, snapshot) { return buildSelector( - title: t.settings.general.theme, + title: t.settings.theme, items: [ - SelectItem( - value: 'system', - label: t.settings.general.theme_auto), - SelectItem( - value: 'light', - label: t.settings.general.theme_light), - SelectItem( - value: 'dark', label: t.settings.general.theme_dark) + SelectItem(value: 'system', label: t.settings.theme_auto), + SelectItem(value: 'light', label: t.settings.theme_light), + SelectItem(value: 'dark', label: t.settings.theme_dark) ], selected: snapshot.data ?? 'system', onChanged: (value) { @@ -154,8 +149,8 @@ class _AdvancedSettingsPageState extends State { initialData: true, builder: (context, snapshot) { return SwitchListTile( - title: Text(t.settings.general.amoled_mode), - subtitle: Text(t.settings.general.amoled_mode_descr), + title: Text(t.settings.amoled_mode), + subtitle: Text(t.settings.amoled_mode_descr), value: snapshot.data!, onChanged: Theme.of(context).brightness == Brightness.light ? null @@ -174,8 +169,8 @@ class _AdvancedSettingsPageState extends State { initialData: true, builder: (context, snapshot) { return SwitchListTile( - title: Text(t.settings.general.dynamic_colors), - subtitle: Text(t.settings.general.dynamic_colors_descr), + title: Text(t.settings.dynamic_colors), + subtitle: Text(t.settings.dynamic_colors_descr), value: snapshot.data!, onChanged: (bool value) { if (value) { @@ -217,8 +212,8 @@ class _AdvancedSettingsPageState extends State { child: Opacity( opacity: snapshot.data! == 'auto' ? 0.4 : 1, child: ExpansionTile( - title: Text(t.settings.general.accent_color), - subtitle: Text(t.settings.general.accent_color_descr), + title: Text(t.settings.accent_color), + subtitle: Text(t.settings.accent_color_descr), controller: expTileController, trailing: SizedBox( height: 46, diff --git a/lib/app/settings/backup_settings_page.dart b/lib/app/settings/backup_settings_page.dart index 8929e085..b8d873aa 100644 --- a/lib/app/settings/backup_settings_page.dart +++ b/lib/app/settings/backup_settings_page.dart @@ -2,13 +2,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/app/settings/export_page.dart'; import 'package:monekin/app/settings/import_csv.dart'; import 'package:monekin/app/settings/settings.page.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/backup/backup_database_service.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/utils/number_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -20,7 +20,7 @@ class BackupSettingsPage extends StatelessWidget { final t = Translations.of(context); return Scaffold( - appBar: AppBar(title: Text(t.settings.data.display)), + appBar: AppBar(title: Text(t.more.data.display)), body: SingleChildScrollView( padding: const EdgeInsets.only(top: 0), child: Column( diff --git a/lib/app/settings/help_us_page.dart b/lib/app/settings/help_us_page.dart index b2ff6646..e742aae8 100644 --- a/lib/app/settings/help_us_page.dart +++ b/lib/app/settings/help_us_page.dart @@ -15,7 +15,7 @@ class HelpUsPage extends StatelessWidget { final iapConnection = IAPConnection.instance; return Scaffold( - appBar: AppBar(title: Text(t.settings.help_us.display)), + appBar: AppBar(title: Text(t.more.help_us.display)), body: SingleChildScrollView( child: Column( children: [ @@ -34,37 +34,49 @@ class HelpUsPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - t.settings.help_us.thanks, + t.more.help_us.thanks, style: Theme.of(context).textTheme.headlineSmall, ), - Text(t.settings.help_us.thanks_long) + Text(t.more.help_us.thanks_long) ], ), ) ], ), ), - createSettingItem(context, - title: t.settings.help_us.rate_us, - subtitle: t.settings.help_us.rate_us_descr, - icon: Icons.star_rounded, onTap: () { - openExternalURL(context, - 'https://play.google.com/store/apps/details?id=com.monekin.app'); - }), - createSettingItem(context, - title: t.settings.help_us.share, - subtitle: t.settings.help_us.share_descr, - icon: Icons.share, onTap: () { - Share.share( - '${t.settings.help_us.share_text}: https://play.google.com/store/apps/details?id=com.monekin.app'); - }), - createSettingItem(context, - title: t.settings.help_us.report, - icon: Icons.rate_review_outlined, onTap: () { - openExternalURL(context, - 'https://github.com/enrique-lozano/Monekin/issues/new/choose'); - }), - const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + createSettingItem(context, + title: t.more.help_us.rate_us, + subtitle: t.more.help_us.rate_us_descr, + icon: Icons.star_rounded, + mainAxis: Axis.horizontal, onTap: () { + openExternalURL(context, + 'https://play.google.com/store/apps/details?id=com.monekin.app'); + }), + const SizedBox(height: 8), + createSettingItem(context, + title: t.more.help_us.share, + subtitle: t.more.help_us.share_descr, + icon: Icons.share, + mainAxis: Axis.horizontal, onTap: () { + Share.share( + '${t.more.help_us.share_text}: https://play.google.com/store/apps/details?id=com.monekin.app'); + }), + const SizedBox(height: 8), + createSettingItem(context, + title: t.more.help_us.report, + icon: Icons.rate_review_outlined, + mainAxis: Axis.horizontal, onTap: () { + openExternalURL(context, + 'https://github.com/enrique-lozano/Monekin/issues/new/choose'); + }), + ], + ), + ), DonateButton(iapConnection: iapConnection) ], ), diff --git a/lib/app/settings/purchases/donate_button.dart b/lib/app/settings/purchases/donate_button.dart index 37cf0cc5..0ef35fc9 100644 --- a/lib/app/settings/purchases/donate_button.dart +++ b/lib/app/settings/purchases/donate_button.dart @@ -45,7 +45,7 @@ class _DonateButtonState extends State { }, onDone: () { _subscription?.cancel(); }, onError: (error) { - showSnackbarMessage(context, t.settings.help_us.donate_err); + showSnackbarMessage(context, t.more.help_us.donate_err); }) as StreamSubscription>; } @@ -57,9 +57,9 @@ class _DonateButtonState extends State { // LOADING } else { if (purchaseDetails.status == PurchaseStatus.error) { - showSnackbarMessage(context, t.settings.help_us.donate_err); + showSnackbarMessage(context, t.more.help_us.donate_err); } else if (purchaseDetails.status == PurchaseStatus.purchased) { - showSnackbarMessage(context, t.settings.help_us.donate_success); + showSnackbarMessage(context, t.more.help_us.donate_success); } if (purchaseDetails.pendingCompletePurchase) { await InAppPurchase.instance.completePurchase(purchaseDetails); @@ -151,11 +151,11 @@ class _DonateButtonState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - t.settings.help_us.donate, + t.more.help_us.donate, style: Theme.of(context).textTheme.titleMedium, ), Text( - t.settings.help_us.donate_descr, + t.more.help_us.donate_descr, style: Theme.of(context).textTheme.labelSmall, ), ], diff --git a/lib/app/settings/purchases/in_app_purchase.dart b/lib/app/settings/purchases/in_app_purchase.dart index 91e60a5b..b2737d1d 100644 --- a/lib/app/settings/purchases/in_app_purchase.dart +++ b/lib/app/settings/purchases/in_app_purchase.dart @@ -8,11 +8,15 @@ class IAPConnection { /// Overwrite the deafult method to check if the platform is supported static Future isAvailable() async { - final instanceAv = await _instance?.isAvailable(); + try { + final instanceAv = await _instance?.isAvailable(); - return instanceAv != null && - instanceAv == true && - (Platform.isIOS || Platform.isAndroid); + return instanceAv != null && + instanceAv == true && + (Platform.isIOS || Platform.isAndroid); + } catch (e) { + return Future.value(false); + } } static set instance(InAppPurchase value) { diff --git a/lib/app/settings/settings.page.dart b/lib/app/settings/settings.page.dart index b79bcd67..40f7ffb3 100644 --- a/lib/app/settings/settings.page.dart +++ b/lib/app/settings/settings.page.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:monekin/app/budgets/budgets_page.dart'; import 'package:monekin/app/categories/categories_list.dart'; import 'package:monekin/app/currencies/currency_manager.dart'; import 'package:monekin/app/settings/about_page.dart'; import 'package:monekin/app/settings/appearance_settings_page.dart'; import 'package:monekin/app/settings/backup_settings_page.dart'; -import 'package:monekin/app/settings/edit_profile_modal.dart'; import 'package:monekin/app/settings/help_us_page.dart'; +import 'package:monekin/app/stats/stats_page.dart'; import 'package:monekin/app/tags/tag_list.page.dart'; -import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; -import 'package:monekin/core/presentation/widgets/skeleton.dart'; -import 'package:monekin/core/presentation/widgets/user_avatar.dart'; +import 'package:monekin/app/transactions/recurrent_transactions_page.dart'; +import 'package:monekin/core/presentation/responsive/breakpoints.dart'; +import 'package:monekin/core/presentation/widgets/tappable.dart'; import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/utils/color_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -36,25 +37,83 @@ Widget createListSeparator(BuildContext context, String title) { ); } -ListTile createSettingItem(BuildContext context, - {required String title, - String? subtitle, - required IconData icon, - required Function() onTap}) { - return ListTile( - title: Text(title), - subtitle: subtitle != null ? Text(subtitle) : null, - leading: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, +Widget createSettingItem( + BuildContext context, { + required String title, + String? subtitle, + required IconData icon, + required Function() onTap, + Axis mainAxis = Axis.vertical, + bool isPrimary = false, +}) { + return Tappable( + bgColor: isPrimary + ? Theme.of(context).brightness == Brightness.light + ? AppColors.of(context).primary.lighten(0.8) + : AppColors.of(context).primary.darken(0.8) + : null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + width: 2, + color: isPrimary + ? AppColors.of(context).primary + : Theme.of(context).dividerColor, + ), + ), + onTap: () => onTap(), + child: Container( + padding: EdgeInsets.symmetric( + vertical: mainAxis == Axis.horizontal ? 12 : 12, + horizontal: mainAxis == Axis.horizontal ? 16 : 16, + ), + child: Flex( + direction: mainAxis, + //mainAxisSize: MainAxisSize.min, children: [ Icon( icon, - color: AppColors.of(context).primary, + color: isPrimary ? AppColors.of(context).primary : null, + size: mainAxis == Axis.horizontal ? 24 : 28, + // color: AppColors.of(context).primary, ), + if (mainAxis == Axis.horizontal) const SizedBox(width: 12), + if (mainAxis == Axis.vertical) const SizedBox(height: 8), + Builder(builder: (context) { + final toReturn = Column( + crossAxisAlignment: mainAxis == Axis.vertical + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium, + softWrap: false, + overflow: TextOverflow.fade, + textAlign: mainAxis == Axis.vertical + ? TextAlign.center + : TextAlign.start, + ), + if (subtitle != null) + Text( + subtitle, + style: Theme.of(context).textTheme.bodySmall, + textAlign: mainAxis == Axis.vertical + ? TextAlign.center + : TextAlign.start, + ) + ], + ); + + return mainAxis == Axis.vertical + ? toReturn + : Expanded(child: toReturn); + }) ], ), - onTap: () => onTap()); + ), + ); } class _SettingsPageState extends State { @@ -62,139 +121,146 @@ class _SettingsPageState extends State { Widget build(BuildContext context) { final t = Translations.of(context); - final settingService = UserSettingService.instance; - return Scaffold( appBar: AppBar( - title: Text(t.settings.title), + title: Text(t.more.title_long), ), body: SingleChildScrollView( - padding: const EdgeInsets.only(top: 8), + padding: + const EdgeInsets.only(top: 8, left: 16, right: 16, bottom: 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ListTile( - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - showDragHandle: true, - builder: (context) { - return const EditProfileModal(); - }); - }, - title: Text(t.settings.edit_profile), - subtitle: StreamBuilder( - stream: settingService.getSetting(SettingKey.userName), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Skeleton(width: 70, height: 12); - } - - return Text(snapshot.data!); - }), - leading: StreamBuilder( - stream: UserSettingService.instance - .getSetting(SettingKey.avatar), - builder: (context, snapshot) { - return UserAvatar(avatar: snapshot.data); - })), - const SizedBox(height: 12), createSettingItem( context, - title: t.general.categories, - subtitle: t.settings.general.categories_descr, - icon: Icons.category_rounded, - onTap: () => - RouteUtils.pushRoute(context, const CategoriesListPage()), + isPrimary: true, + title: t.more.help_us.display, + subtitle: t.more.help_us.description, + icon: Icons.favorite_rounded, + mainAxis: Axis.horizontal, + onTap: () => RouteUtils.pushRoute(context, const HelpUsPage()), ), + const SizedBox(height: 8), createSettingItem( context, - title: t.tags.display(n: 10), - subtitle: t.settings.general.categories_descr, - icon: Icons.label_outline_rounded, - onTap: () => RouteUtils.pushRoute(context, const TagListPage()), + title: t.settings.title_long, + subtitle: t.settings.description, + icon: Icons.palette_outlined, + mainAxis: Axis.horizontal, + onTap: () => + RouteUtils.pushRoute(context, const AdvancedSettingsPage()), ), + const SizedBox(height: 8), createSettingItem( context, title: t.currencies.currency_manager, subtitle: t.currencies.currency_manager_descr, icon: Icons.currency_exchange, + mainAxis: Axis.horizontal, onTap: () => RouteUtils.pushRoute(context, const CurrencyManagerPage()), ), + const SizedBox(height: 8), createSettingItem( context, - title: t.settings.general.appearance, - subtitle: t.settings.general.appearance_descr, - icon: Icons.palette_outlined, - onTap: () => - RouteUtils.pushRoute(context, const AdvancedSettingsPage()), - ), - createSettingItem( - context, - title: t.settings.data.display, - subtitle: t.settings.data.display_descr, + title: t.more.data.display, + subtitle: t.more.data.display_descr, icon: Icons.storage_rounded, + mainAxis: Axis.horizontal, onTap: () => RouteUtils.pushRoute(context, const BackupSettingsPage()), ), + const SizedBox(height: 8), createSettingItem( context, - title: t.settings.about_us.display, - subtitle: t.settings.about_us.description, + title: t.more.about_us.display, + subtitle: t.more.about_us.description, icon: Icons.info_outline_rounded, + mainAxis: Axis.horizontal, onTap: () => RouteUtils.pushRoute(context, const AboutPage()), ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.all(16), - child: InkWell( - radius: 8, - onTap: () => - RouteUtils.pushRoute(context, const HelpUsPage()), - child: Card( - clipBehavior: Clip.hardEdge, - margin: const EdgeInsets.all(0), - color: Theme.of(context).brightness == Brightness.light - ? AppColors.of(context).primary.lighten(0.8) - : AppColors.of(context).primary.withOpacity(0.2), - shape: RoundedRectangleBorder( - side: BorderSide( - color: AppColors.of(context).primary, width: 2), - borderRadius: BorderRadius.circular(8)), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 12), - child: Row( - children: [ - Icon( - Icons.favorite, - color: AppColors.of(context).primary, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - t.settings.help_us.display, - style: - Theme.of(context).textTheme.titleMedium, - ), - Text( - t.settings.help_us.description, - style: Theme.of(context).textTheme.labelSmall, - ), - ], - ), - ), - ], + if (BreakPoint.of(context).isSmallerThan(BreakpointID.md)) ...[ + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: createSettingItem( + context, + title: t.stats.title, + icon: Icons.area_chart_rounded, + onTap: () => + RouteUtils.pushRoute(context, const StatsPage()), ), ), - ), + const SizedBox(width: 8), + Expanded( + child: createSettingItem( + context, + title: t.budgets.title, + icon: Icons.pie_chart_rounded, + onTap: () => + RouteUtils.pushRoute(context, const BudgetsPage()), + ), + ), + const SizedBox(width: 8), + Expanded( + child: createSettingItem( + context, + title: t.recurrent_transactions.title_short, + icon: Icons.repeat_rounded, + onTap: () => RouteUtils.pushRoute( + context, const RecurrentTransactionPage()), + ), + ), + ], ), - ) + ], + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: createSettingItem( + context, + title: t.general.categories, + icon: Icons.category_rounded, + onTap: () => RouteUtils.pushRoute( + context, const CategoriesListPage()), + ), + ), + const SizedBox(width: 8), + Expanded( + child: createSettingItem( + context, + title: t.tags.display(n: 10), + icon: Icons.label_outline_rounded, + onTap: () => + RouteUtils.pushRoute(context, const TagListPage()), + ), + ), + const SizedBox(width: 8), + Expanded( + child: createSettingItem( + context, + title: t.general.accounts, + icon: Icons.account_balance_wallet_rounded, + onTap: () => + RouteUtils.pushRoute(context, const TagListPage()), + ), + ), + if (BreakPoint.of(context).isLargerThan(BreakpointID.sm)) ...[ + const SizedBox(width: 8), + Expanded( + child: createSettingItem( + context, + title: t.recurrent_transactions.title_short, + icon: Icons.repeat_rounded, + onTap: () => RouteUtils.pushRoute( + context, const RecurrentTransactionPage()), + ), + ), + ] + ], + ), ], ), )); diff --git a/lib/core/routes/destinations.dart b/lib/core/routes/destinations.dart index 2ccaa8cc..1a396177 100644 --- a/lib/core/routes/destinations.dart +++ b/lib/core/routes/destinations.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:monekin/app/accounts/all_accounts_page.dart'; import 'package:monekin/app/budgets/budgets_page.dart'; import 'package:monekin/app/home/dashboard.page.dart'; import 'package:monekin/app/settings/settings.page.dart'; import 'package:monekin/app/stats/stats_page.dart'; -import 'package:monekin/app/transactions/recurrent_transactions_page.dart'; import 'package:monekin/app/transactions/transactions.page.dart'; import 'package:monekin/core/presentation/responsive/breakpoints.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -87,38 +85,37 @@ List getAllDestinations( selectedIcon: Icons.calculate, destination: const BudgetsPage(), ), - MainMenuDestination( + /* MainMenuDestination( AppMenuDestinationsID.accounts, label: t.general.accounts, icon: Icons.account_balance_rounded, destination: const AllAccountsPage(), - ), + ), */ MainMenuDestination( AppMenuDestinationsID.transactions, label: t.transaction.display(n: 10), icon: Icons.app_registration_rounded, - destination: TransactionsPage(), + destination: const TransactionsPage(), ), - MainMenuDestination( + /* MainMenuDestination( AppMenuDestinationsID.recurrentTransactions, label: shortLabels ? t.recurrent_transactions.title_short : t.recurrent_transactions.title, icon: Icons.auto_mode_rounded, - destination: RecurrentTransactionPage(), + destination: const RecurrentTransactionPage(), + ), */ + MainMenuDestination( + AppMenuDestinationsID.stats, + label: t.stats.title, + icon: Icons.auto_graph_rounded, + destination: const StatsPage(), ), - if (MediaQuery.of(context).size.height > 650) - MainMenuDestination( - AppMenuDestinationsID.stats, - label: t.stats.title, - icon: Icons.auto_graph_rounded, - destination: StatsPage(), - ), MainMenuDestination( AppMenuDestinationsID.settings, - label: t.settings.title, - selectedIcon: Icons.settings, - icon: Icons.settings_outlined, + label: t.more.title, + selectedIcon: Icons.more_horiz_rounded, + icon: Icons.more_horiz_rounded, destination: const SettingsPage(), ), ]; diff --git a/lib/i18n/strings_en.json b/lib/i18n/strings_en.json index c1aa3b46..7160fdbe 100644 --- a/lib/i18n/strings_en.json +++ b/lib/i18n/strings_en.json @@ -595,35 +595,32 @@ } }, "SETTINGS": { - "title": "Settings", - "edit-profile": "Edit profile", - "display-name": "User name", - "lang": "App language", + "title_long": "Settings and appearance", + "title_short": "Settings", + "description": "App theme, texts and other general settings", + "edit_profile": "Edit profile", + "lang.section": "Language and texts", + "lang.title": "App language", "lang.descr": "Language in which the texts will be displayed in the app", "locale": "Region", "locale.descr": "Set the format to use for dates, numbers...", "locale.warn": "When changing region the app will update", "first_day_of_week": "First day of week", - "GENERAL": { - "categories.descr": "Create and edit categories to your liking", - "other": "Advanced settings", - "other.descr": "Other general app customization settings", - "appearance": "Appearance", - "appearance.descr": "App theme, texts...", - "app_colors": "App colors", - "theme": "Theme", - "theme.auto": "Defined by the system", - "theme.light": "Light", - "theme.dark": "Dark", - "amoled-mode": "AMOLED mode", - "amoled-mode.descr": "Use a pure black wallpaper when possible. This will slightly help the battery of devices with AMOLED screens", - "dynamic-colors": "Dynamic colors", - "dynamic-colors.descr": "Use your system accent color whenever possible", - "accent-color": "Accent color", - "accent-color.descr": "Choose the color the app will use to emphasize certain parts of the interface", - "prefer-calc": "Prefer calculator", - "prefer-calc-descr": "Displays a calculator on the add/edit transaction page that will simplify actions on devices without a keyboard" - }, + "theme_and_colors": "Theme and colors", + "theme": "Theme", + "theme.auto": "Defined by the system", + "theme.light": "Light", + "theme.dark": "Dark", + "amoled-mode": "AMOLED mode", + "amoled-mode.descr": "Use a pure black wallpaper when possible. This will slightly help the battery of devices with AMOLED screens", + "dynamic-colors": "Dynamic colors", + "dynamic-colors.descr": "Use your system accent color whenever possible", + "accent-color": "Accent color", + "accent-color.descr": "Choose the color the app will use to emphasize certain parts of the interface" + }, + "MORE": { + "title": "More", + "title_long": "More actions", "DATA": { "display": "Data", "display.descr": "Export and import your data so you don't lose anything", diff --git a/lib/i18n/strings_es.json b/lib/i18n/strings_es.json index 499fc6cd..65473658 100644 --- a/lib/i18n/strings_es.json +++ b/lib/i18n/strings_es.json @@ -599,36 +599,32 @@ } }, "SETTINGS": { - "title": "Ajustes", - "edit-profile": "Editar perfil", - "display-name": "Nombre de usuario", - "lang": "Idioma de la aplicación", - "lang.descr": "Idioma en el que se mostrarán los textos en la app", + "title_long": "Configuración y apariencia", + "title_short": "Configuración", + "description": "Tema de la aplicación, textos y otras configuraciones generales", + "edit_profile": "Editar perfil", + "lang.section": "Idioma y textos", + "lang.title": "Idioma de la aplicación", + "lang.descr": "Idioma en el que se mostrarán los textos en la aplicación", "locale": "Región", - "locale.descr": "Establece el formato a usar para fechas, números...", - "locale.warn": "Al cambiar de región la app se refrescará", + "locale.descr": "Establecer el formato a utilizar para fechas, números...", + "locale.warn": "Al cambiar la región, la aplicación se actualizará", "first_day_of_week": "Primer día de la semana", - "GENERAL": { - "display": "Ajustes generales", - "categories.descr": "Crea y edita categorías a tu gusto", - "other": "Ajustes avanzados", - "other.descr": "Otros ajustes generales de personalización de la app", - "appearance": "Apariencia", - "appearance.descr": "Tema de la aplicación, textos...", - "app_colors": "Colores de la aplicación", - "theme": "Tema", - "theme.auto": "Definido por el sistema", - "theme.light": "Claro", - "theme.dark": "Oscuro", - "amoled-mode": "Modo AMOLED", - "amoled-mode.descr": "Usa un fondo de pantalla negro puro cuando sea posible. Esto ayudará ligeramente a la bateria de dispositivos con pantallas AMOLED", - "dynamic-colors": "Colores dinámicos", - "dynamic-colors.descr": "Usa el color de acentuación de su sistema siempre que sea posible", - "accent-color": "Color de acentuación", - "accent-color.descr": "Elige el color que usará la app para enfatizar ciertas partes de la interfaz", - "prefer-calc": "Preferir calculadora", - "prefer-calc-descr": "Muestra en la página de adición/edición de transacciones una calculadora que simplificará las acciones en dispositivos sin teclado" - }, + "theme_and_colors": "Tema y colores", + "theme": "Tema", + "theme.auto": "Definido por el sistema", + "theme.light": "Claro", + "theme.dark": "Oscuro", + "amoled-mode": "Modo AMOLED", + "amoled-mode.descr": "Usar un fondo negro puro cuando sea posible. Esto ayudará ligeramente a la batería de dispositivos con pantallas AMOLED", + "dynamic-colors": "Colores dinámicos", + "dynamic-colors.descr": "Usar el color de acento de su sistema siempre que sea posible", + "accent-color": "Color de acento", + "accent-color.descr": "Elegir el color que la aplicación usará para enfatizar ciertas partes de la interfaz" + }, + "MORE": { + "title": "Más", + "title_long": "Más acciones", "DATA": { "display": "Datos", "display.descr": "Exporta y importa tus datos para no perder nada", diff --git a/lib/i18n/translations.g.dart b/lib/i18n/translations.g.dart index b6fb8dba..5424d59f 100644 --- a/lib/i18n/translations.g.dart +++ b/lib/i18n/translations.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 1026 (513 per locale) +/// Strings: 1019 (509 per locale) /// -/// Built on 2024-02-13 at 15:34 UTC +/// Built on 2024-02-15 at 11:04 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -173,6 +173,7 @@ class _TranslationsEn implements BaseTranslations { late final _TranslationsBudgetsEn budgets = _TranslationsBudgetsEn._(_root); late final _TranslationsBackupEn backup = _TranslationsBackupEn._(_root); late final _TranslationsSettingsEn settings = _TranslationsSettingsEn._(_root); + late final _TranslationsMoreEn more = _TranslationsMoreEn._(_root); late final _TranslationsLangEn lang = _TranslationsLangEn._(_root); } @@ -531,19 +532,42 @@ class _TranslationsSettingsEn { final _TranslationsEn _root; // ignore: unused_field // Translations - String get title => 'Settings'; + String get title_long => 'Settings and appearance'; + String get title_short => 'Settings'; + String get description => 'App theme, texts and other general settings'; String get edit_profile => 'Edit profile'; - String get display_name => 'User name'; - String get lang => 'App language'; + String get lang_section => 'Language and texts'; + String get lang_title => 'App language'; String get lang_descr => 'Language in which the texts will be displayed in the app'; String get locale => 'Region'; String get locale_descr => 'Set the format to use for dates, numbers...'; String get locale_warn => 'When changing region the app will update'; String get first_day_of_week => 'First day of week'; - late final _TranslationsSettingsGeneralEn general = _TranslationsSettingsGeneralEn._(_root); - late final _TranslationsSettingsDataEn data = _TranslationsSettingsDataEn._(_root); - late final _TranslationsSettingsAboutUsEn about_us = _TranslationsSettingsAboutUsEn._(_root); - late final _TranslationsSettingsHelpUsEn help_us = _TranslationsSettingsHelpUsEn._(_root); + String get theme_and_colors => 'Theme and colors'; + String get theme => 'Theme'; + String get theme_auto => 'Defined by the system'; + String get theme_light => 'Light'; + String get theme_dark => 'Dark'; + String get amoled_mode => 'AMOLED mode'; + String get amoled_mode_descr => 'Use a pure black wallpaper when possible. This will slightly help the battery of devices with AMOLED screens'; + String get dynamic_colors => 'Dynamic colors'; + String get dynamic_colors_descr => 'Use your system accent color whenever possible'; + String get accent_color => 'Accent color'; + String get accent_color_descr => 'Choose the color the app will use to emphasize certain parts of the interface'; +} + +// Path: more +class _TranslationsMoreEn { + _TranslationsMoreEn._(this._root); + + final _TranslationsEn _root; // ignore: unused_field + + // Translations + String get title => 'More'; + String get title_long => 'More actions'; + late final _TranslationsMoreDataEn data = _TranslationsMoreDataEn._(_root); + late final _TranslationsMoreAboutUsEn about_us = _TranslationsMoreAboutUsEn._(_root); + late final _TranslationsMoreHelpUsEn help_us = _TranslationsMoreHelpUsEn._(_root); } // Path: lang @@ -1064,36 +1088,9 @@ class _TranslationsBackupAboutEn { String get size => 'Size'; } -// Path: settings.general -class _TranslationsSettingsGeneralEn { - _TranslationsSettingsGeneralEn._(this._root); - - final _TranslationsEn _root; // ignore: unused_field - - // Translations - String get categories_descr => 'Create and edit categories to your liking'; - String get other => 'Advanced settings'; - String get other_descr => 'Other general app customization settings'; - String get appearance => 'Appearance'; - String get appearance_descr => 'App theme, texts...'; - String get app_colors => 'App colors'; - String get theme => 'Theme'; - String get theme_auto => 'Defined by the system'; - String get theme_light => 'Light'; - String get theme_dark => 'Dark'; - String get amoled_mode => 'AMOLED mode'; - String get amoled_mode_descr => 'Use a pure black wallpaper when possible. This will slightly help the battery of devices with AMOLED screens'; - String get dynamic_colors => 'Dynamic colors'; - String get dynamic_colors_descr => 'Use your system accent color whenever possible'; - String get accent_color => 'Accent color'; - String get accent_color_descr => 'Choose the color the app will use to emphasize certain parts of the interface'; - String get prefer_calc => 'Prefer calculator'; - String get prefer_calc_descr => 'Displays a calculator on the add/edit transaction page that will simplify actions on devices without a keyboard'; -} - -// Path: settings.data -class _TranslationsSettingsDataEn { - _TranslationsSettingsDataEn._(this._root); +// Path: more.data +class _TranslationsMoreDataEn { + _TranslationsMoreDataEn._(this._root); final _TranslationsEn _root; // ignore: unused_field @@ -1107,22 +1104,22 @@ class _TranslationsSettingsDataEn { String get delete_all_message2 => 'By deleting an account you will delete all your stored personal data. Your accounts, transactions, budgets and categories will be deleted and cannot be recovered. Do you agree?'; } -// Path: settings.about_us -class _TranslationsSettingsAboutUsEn { - _TranslationsSettingsAboutUsEn._(this._root); +// Path: more.about_us +class _TranslationsMoreAboutUsEn { + _TranslationsMoreAboutUsEn._(this._root); final _TranslationsEn _root; // ignore: unused_field // Translations String get display => 'App information'; String get description => 'Check out the terms and other relevant information about Monekin. Get in touch with the community by reporting bugs, leaving suggestions...'; - late final _TranslationsSettingsAboutUsLegalEn legal = _TranslationsSettingsAboutUsLegalEn._(_root); - late final _TranslationsSettingsAboutUsProjectEn project = _TranslationsSettingsAboutUsProjectEn._(_root); + late final _TranslationsMoreAboutUsLegalEn legal = _TranslationsMoreAboutUsLegalEn._(_root); + late final _TranslationsMoreAboutUsProjectEn project = _TranslationsMoreAboutUsProjectEn._(_root); } -// Path: settings.help_us -class _TranslationsSettingsHelpUsEn { - _TranslationsSettingsHelpUsEn._(this._root); +// Path: more.help_us +class _TranslationsMoreHelpUsEn { + _TranslationsMoreHelpUsEn._(this._root); final _TranslationsEn _root; // ignore: unused_field @@ -1325,9 +1322,9 @@ class _TranslationsBackupImportManualImportEn { String success({required Object x}) => 'Successfully imported ${x} transactions'; } -// Path: settings.about_us.legal -class _TranslationsSettingsAboutUsLegalEn { - _TranslationsSettingsAboutUsLegalEn._(this._root); +// Path: more.about_us.legal +class _TranslationsMoreAboutUsLegalEn { + _TranslationsMoreAboutUsLegalEn._(this._root); final _TranslationsEn _root; // ignore: unused_field @@ -1338,9 +1335,9 @@ class _TranslationsSettingsAboutUsLegalEn { String get licenses => 'Licenses'; } -// Path: settings.about_us.project -class _TranslationsSettingsAboutUsProjectEn { - _TranslationsSettingsAboutUsProjectEn._(this._root); +// Path: more.about_us.project +class _TranslationsMoreAboutUsProjectEn { + _TranslationsMoreAboutUsProjectEn._(this._root); final _TranslationsEn _root; // ignore: unused_field @@ -1406,6 +1403,7 @@ class _TranslationsEs implements _TranslationsEn { @override late final _TranslationsBudgetsEs budgets = _TranslationsBudgetsEs._(_root); @override late final _TranslationsBackupEs backup = _TranslationsBackupEs._(_root); @override late final _TranslationsSettingsEs settings = _TranslationsSettingsEs._(_root); + @override late final _TranslationsMoreEs more = _TranslationsMoreEs._(_root); @override late final _TranslationsLangEs lang = _TranslationsLangEs._(_root); } @@ -1764,19 +1762,42 @@ class _TranslationsSettingsEs implements _TranslationsSettingsEn { @override final _TranslationsEs _root; // ignore: unused_field // Translations - @override String get title => 'Ajustes'; + @override String get title_long => 'Configuración y apariencia'; + @override String get title_short => 'Configuración'; + @override String get description => 'Tema de la aplicación, textos y otras configuraciones generales'; @override String get edit_profile => 'Editar perfil'; - @override String get display_name => 'Nombre de usuario'; - @override String get lang => 'Idioma de la aplicación'; - @override String get lang_descr => 'Idioma en el que se mostrarán los textos en la app'; + @override String get lang_section => 'Idioma y textos'; + @override String get lang_title => 'Idioma de la aplicación'; + @override String get lang_descr => 'Idioma en el que se mostrarán los textos en la aplicación'; @override String get locale => 'Región'; - @override String get locale_descr => 'Establece el formato a usar para fechas, números...'; - @override String get locale_warn => 'Al cambiar de región la app se refrescará'; + @override String get locale_descr => 'Establecer el formato a utilizar para fechas, números...'; + @override String get locale_warn => 'Al cambiar la región, la aplicación se actualizará'; @override String get first_day_of_week => 'Primer día de la semana'; - @override late final _TranslationsSettingsGeneralEs general = _TranslationsSettingsGeneralEs._(_root); - @override late final _TranslationsSettingsDataEs data = _TranslationsSettingsDataEs._(_root); - @override late final _TranslationsSettingsAboutUsEs about_us = _TranslationsSettingsAboutUsEs._(_root); - @override late final _TranslationsSettingsHelpUsEs help_us = _TranslationsSettingsHelpUsEs._(_root); + @override String get theme_and_colors => 'Tema y colores'; + @override String get theme => 'Tema'; + @override String get theme_auto => 'Definido por el sistema'; + @override String get theme_light => 'Claro'; + @override String get theme_dark => 'Oscuro'; + @override String get amoled_mode => 'Modo AMOLED'; + @override String get amoled_mode_descr => 'Usar un fondo negro puro cuando sea posible. Esto ayudará ligeramente a la batería de dispositivos con pantallas AMOLED'; + @override String get dynamic_colors => 'Colores dinámicos'; + @override String get dynamic_colors_descr => 'Usar el color de acento de su sistema siempre que sea posible'; + @override String get accent_color => 'Color de acento'; + @override String get accent_color_descr => 'Elegir el color que la aplicación usará para enfatizar ciertas partes de la interfaz'; +} + +// Path: more +class _TranslationsMoreEs implements _TranslationsMoreEn { + _TranslationsMoreEs._(this._root); + + @override final _TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Más'; + @override String get title_long => 'Más acciones'; + @override late final _TranslationsMoreDataEs data = _TranslationsMoreDataEs._(_root); + @override late final _TranslationsMoreAboutUsEs about_us = _TranslationsMoreAboutUsEs._(_root); + @override late final _TranslationsMoreHelpUsEs help_us = _TranslationsMoreHelpUsEs._(_root); } // Path: lang @@ -2297,37 +2318,9 @@ class _TranslationsBackupAboutEs implements _TranslationsBackupAboutEn { @override String get size => 'Tamaño'; } -// Path: settings.general -class _TranslationsSettingsGeneralEs implements _TranslationsSettingsGeneralEn { - _TranslationsSettingsGeneralEs._(this._root); - - @override final _TranslationsEs _root; // ignore: unused_field - - // Translations - @override String get display => 'Ajustes generales'; - @override String get categories_descr => 'Crea y edita categorías a tu gusto'; - @override String get other => 'Ajustes avanzados'; - @override String get other_descr => 'Otros ajustes generales de personalización de la app'; - @override String get appearance => 'Apariencia'; - @override String get appearance_descr => 'Tema de la aplicación, textos...'; - @override String get app_colors => 'Colores de la aplicación'; - @override String get theme => 'Tema'; - @override String get theme_auto => 'Definido por el sistema'; - @override String get theme_light => 'Claro'; - @override String get theme_dark => 'Oscuro'; - @override String get amoled_mode => 'Modo AMOLED'; - @override String get amoled_mode_descr => 'Usa un fondo de pantalla negro puro cuando sea posible. Esto ayudará ligeramente a la bateria de dispositivos con pantallas AMOLED'; - @override String get dynamic_colors => 'Colores dinámicos'; - @override String get dynamic_colors_descr => 'Usa el color de acentuación de su sistema siempre que sea posible'; - @override String get accent_color => 'Color de acentuación'; - @override String get accent_color_descr => 'Elige el color que usará la app para enfatizar ciertas partes de la interfaz'; - @override String get prefer_calc => 'Preferir calculadora'; - @override String get prefer_calc_descr => 'Muestra en la página de adición/edición de transacciones una calculadora que simplificará las acciones en dispositivos sin teclado'; -} - -// Path: settings.data -class _TranslationsSettingsDataEs implements _TranslationsSettingsDataEn { - _TranslationsSettingsDataEs._(this._root); +// Path: more.data +class _TranslationsMoreDataEs implements _TranslationsMoreDataEn { + _TranslationsMoreDataEs._(this._root); @override final _TranslationsEs _root; // ignore: unused_field @@ -2341,22 +2334,22 @@ class _TranslationsSettingsDataEs implements _TranslationsSettingsDataEn { @override String get delete_all_message2 => 'Al eliminar una cuenta eliminarás todos tus datos personales almacenados. Tus cuentas, transacciones, presupuestos y categorías serán borrados y no podrán ser recuperados. ¿Estas de acuerdo?'; } -// Path: settings.about_us -class _TranslationsSettingsAboutUsEs implements _TranslationsSettingsAboutUsEn { - _TranslationsSettingsAboutUsEs._(this._root); +// Path: more.about_us +class _TranslationsMoreAboutUsEs implements _TranslationsMoreAboutUsEn { + _TranslationsMoreAboutUsEs._(this._root); @override final _TranslationsEs _root; // ignore: unused_field // Translations @override String get display => 'Información de la app'; @override String get description => 'Consulta los terminos y otra información relevante sobre Monekin. Ponte en contacto con la comunidad reportando errores, dejando sugerencias...'; - @override late final _TranslationsSettingsAboutUsLegalEs legal = _TranslationsSettingsAboutUsLegalEs._(_root); - @override late final _TranslationsSettingsAboutUsProjectEs project = _TranslationsSettingsAboutUsProjectEs._(_root); + @override late final _TranslationsMoreAboutUsLegalEs legal = _TranslationsMoreAboutUsLegalEs._(_root); + @override late final _TranslationsMoreAboutUsProjectEs project = _TranslationsMoreAboutUsProjectEs._(_root); } -// Path: settings.help_us -class _TranslationsSettingsHelpUsEs implements _TranslationsSettingsHelpUsEn { - _TranslationsSettingsHelpUsEs._(this._root); +// Path: more.help_us +class _TranslationsMoreHelpUsEs implements _TranslationsMoreHelpUsEn { + _TranslationsMoreHelpUsEs._(this._root); @override final _TranslationsEs _root; // ignore: unused_field @@ -2560,9 +2553,9 @@ class _TranslationsBackupImportManualImportEs implements _TranslationsBackupImpo ]; } -// Path: settings.about_us.legal -class _TranslationsSettingsAboutUsLegalEs implements _TranslationsSettingsAboutUsLegalEn { - _TranslationsSettingsAboutUsLegalEs._(this._root); +// Path: more.about_us.legal +class _TranslationsMoreAboutUsLegalEs implements _TranslationsMoreAboutUsLegalEn { + _TranslationsMoreAboutUsLegalEs._(this._root); @override final _TranslationsEs _root; // ignore: unused_field @@ -2573,9 +2566,9 @@ class _TranslationsSettingsAboutUsLegalEs implements _TranslationsSettingsAboutU @override String get licenses => 'Licencias'; } -// Path: settings.about_us.project -class _TranslationsSettingsAboutUsProjectEs implements _TranslationsSettingsAboutUsProjectEn { - _TranslationsSettingsAboutUsProjectEs._(this._root); +// Path: more.about_us.project +class _TranslationsMoreAboutUsProjectEs implements _TranslationsMoreAboutUsProjectEn { + _TranslationsMoreAboutUsProjectEs._(this._root); @override final _TranslationsEs _root; // ignore: unused_field @@ -3128,64 +3121,61 @@ extension on _TranslationsEn { case 'backup.about.modify_date': return 'Last modified'; case 'backup.about.last_backup': return 'Last backup'; case 'backup.about.size': return 'Size'; - case 'settings.title': return 'Settings'; + case 'settings.title_long': return 'Settings and appearance'; + case 'settings.title_short': return 'Settings'; + case 'settings.description': return 'App theme, texts and other general settings'; case 'settings.edit_profile': return 'Edit profile'; - case 'settings.display_name': return 'User name'; - case 'settings.lang': return 'App language'; + case 'settings.lang_section': return 'Language and texts'; + case 'settings.lang_title': return 'App language'; case 'settings.lang_descr': return 'Language in which the texts will be displayed in the app'; case 'settings.locale': return 'Region'; case 'settings.locale_descr': return 'Set the format to use for dates, numbers...'; case 'settings.locale_warn': return 'When changing region the app will update'; case 'settings.first_day_of_week': return 'First day of week'; - case 'settings.general.categories_descr': return 'Create and edit categories to your liking'; - case 'settings.general.other': return 'Advanced settings'; - case 'settings.general.other_descr': return 'Other general app customization settings'; - case 'settings.general.appearance': return 'Appearance'; - case 'settings.general.appearance_descr': return 'App theme, texts...'; - case 'settings.general.app_colors': return 'App colors'; - case 'settings.general.theme': return 'Theme'; - case 'settings.general.theme_auto': return 'Defined by the system'; - case 'settings.general.theme_light': return 'Light'; - case 'settings.general.theme_dark': return 'Dark'; - case 'settings.general.amoled_mode': return 'AMOLED mode'; - case 'settings.general.amoled_mode_descr': return 'Use a pure black wallpaper when possible. This will slightly help the battery of devices with AMOLED screens'; - case 'settings.general.dynamic_colors': return 'Dynamic colors'; - case 'settings.general.dynamic_colors_descr': return 'Use your system accent color whenever possible'; - case 'settings.general.accent_color': return 'Accent color'; - case 'settings.general.accent_color_descr': return 'Choose the color the app will use to emphasize certain parts of the interface'; - case 'settings.general.prefer_calc': return 'Prefer calculator'; - case 'settings.general.prefer_calc_descr': return 'Displays a calculator on the add/edit transaction page that will simplify actions on devices without a keyboard'; - case 'settings.data.display': return 'Data'; - case 'settings.data.display_descr': return 'Export and import your data so you don\'t lose anything'; - case 'settings.data.delete_all': return 'Delete my data'; - case 'settings.data.delete_all_header1': return 'Stop right there padawan ⚠️⚠️'; - case 'settings.data.delete_all_message1': return 'Are you sure you want to continue? All your data will be permanently deleted and cannot be recovered'; - case 'settings.data.delete_all_header2': return 'One last step ⚠️⚠️'; - case 'settings.data.delete_all_message2': return 'By deleting an account you will delete all your stored personal data. Your accounts, transactions, budgets and categories will be deleted and cannot be recovered. Do you agree?'; - case 'settings.about_us.display': return 'App information'; - case 'settings.about_us.description': return 'Check out the terms and other relevant information about Monekin. Get in touch with the community by reporting bugs, leaving suggestions...'; - case 'settings.about_us.legal.display': return 'Legal information'; - case 'settings.about_us.legal.privacy': return 'Privacy policy'; - case 'settings.about_us.legal.terms': return 'Terms of use'; - case 'settings.about_us.legal.licenses': return 'Licenses'; - case 'settings.about_us.project.display': return 'Project'; - case 'settings.about_us.project.contributors': return 'Collaborators'; - case 'settings.about_us.project.contributors_descr': return 'All the developers who have made Monekin grow'; - case 'settings.about_us.project.contact': return 'Contact us'; - case 'settings.help_us.display': return 'Help us'; - case 'settings.help_us.description': return 'Find out how you can help Monekin become better and better'; - case 'settings.help_us.rate_us': return 'Rate us'; - case 'settings.help_us.rate_us_descr': return 'Any rate is welcome!'; - case 'settings.help_us.share': return 'Share Monekin'; - case 'settings.help_us.share_descr': return 'Share our app to friends and family'; - case 'settings.help_us.share_text': return 'Monekin! The best personal finance app. Download it here'; - case 'settings.help_us.thanks': return 'Thank you!'; - case 'settings.help_us.thanks_long': return 'Your contributions to Monekin and other open source projects, big and small, make great projects like this possible. Thank you for taking the time to contribute.'; - case 'settings.help_us.donate': return 'Make a donation'; - case 'settings.help_us.donate_descr': return 'With your donation you will help the app continue receiving improvements. What better way than to thank the work done by inviting me to a coffee?'; - case 'settings.help_us.donate_success': return 'Donation made. Thank you very much for your contribution! ❤️'; - case 'settings.help_us.donate_err': return 'Oops! It seems there was an error receiving your payment'; - case 'settings.help_us.report': return 'Report bugs, leave suggestions...'; + case 'settings.theme_and_colors': return 'Theme and colors'; + case 'settings.theme': return 'Theme'; + case 'settings.theme_auto': return 'Defined by the system'; + case 'settings.theme_light': return 'Light'; + case 'settings.theme_dark': return 'Dark'; + case 'settings.amoled_mode': return 'AMOLED mode'; + case 'settings.amoled_mode_descr': return 'Use a pure black wallpaper when possible. This will slightly help the battery of devices with AMOLED screens'; + case 'settings.dynamic_colors': return 'Dynamic colors'; + case 'settings.dynamic_colors_descr': return 'Use your system accent color whenever possible'; + case 'settings.accent_color': return 'Accent color'; + case 'settings.accent_color_descr': return 'Choose the color the app will use to emphasize certain parts of the interface'; + case 'more.title': return 'More'; + case 'more.title_long': return 'More actions'; + case 'more.data.display': return 'Data'; + case 'more.data.display_descr': return 'Export and import your data so you don\'t lose anything'; + case 'more.data.delete_all': return 'Delete my data'; + case 'more.data.delete_all_header1': return 'Stop right there padawan ⚠️⚠️'; + case 'more.data.delete_all_message1': return 'Are you sure you want to continue? All your data will be permanently deleted and cannot be recovered'; + case 'more.data.delete_all_header2': return 'One last step ⚠️⚠️'; + case 'more.data.delete_all_message2': return 'By deleting an account you will delete all your stored personal data. Your accounts, transactions, budgets and categories will be deleted and cannot be recovered. Do you agree?'; + case 'more.about_us.display': return 'App information'; + case 'more.about_us.description': return 'Check out the terms and other relevant information about Monekin. Get in touch with the community by reporting bugs, leaving suggestions...'; + case 'more.about_us.legal.display': return 'Legal information'; + case 'more.about_us.legal.privacy': return 'Privacy policy'; + case 'more.about_us.legal.terms': return 'Terms of use'; + case 'more.about_us.legal.licenses': return 'Licenses'; + case 'more.about_us.project.display': return 'Project'; + case 'more.about_us.project.contributors': return 'Collaborators'; + case 'more.about_us.project.contributors_descr': return 'All the developers who have made Monekin grow'; + case 'more.about_us.project.contact': return 'Contact us'; + case 'more.help_us.display': return 'Help us'; + case 'more.help_us.description': return 'Find out how you can help Monekin become better and better'; + case 'more.help_us.rate_us': return 'Rate us'; + case 'more.help_us.rate_us_descr': return 'Any rate is welcome!'; + case 'more.help_us.share': return 'Share Monekin'; + case 'more.help_us.share_descr': return 'Share our app to friends and family'; + case 'more.help_us.share_text': return 'Monekin! The best personal finance app. Download it here'; + case 'more.help_us.thanks': return 'Thank you!'; + case 'more.help_us.thanks_long': return 'Your contributions to Monekin and other open source projects, big and small, make great projects like this possible. Thank you for taking the time to contribute.'; + case 'more.help_us.donate': return 'Make a donation'; + case 'more.help_us.donate_descr': return 'With your donation you will help the app continue receiving improvements. What better way than to thank the work done by inviting me to a coffee?'; + case 'more.help_us.donate_success': return 'Donation made. Thank you very much for your contribution! ❤️'; + case 'more.help_us.donate_err': return 'Oops! It seems there was an error receiving your payment'; + case 'more.help_us.report': return 'Report bugs, leave suggestions...'; case 'lang.es': return 'Spanish'; case 'lang.en': return 'English'; default: return null; @@ -3719,65 +3709,61 @@ extension on _TranslationsEs { case 'backup.about.modify_date': return 'Última modificación'; case 'backup.about.last_backup': return 'Última copia de seguridad'; case 'backup.about.size': return 'Tamaño'; - case 'settings.title': return 'Ajustes'; + case 'settings.title_long': return 'Configuración y apariencia'; + case 'settings.title_short': return 'Configuración'; + case 'settings.description': return 'Tema de la aplicación, textos y otras configuraciones generales'; case 'settings.edit_profile': return 'Editar perfil'; - case 'settings.display_name': return 'Nombre de usuario'; - case 'settings.lang': return 'Idioma de la aplicación'; - case 'settings.lang_descr': return 'Idioma en el que se mostrarán los textos en la app'; + case 'settings.lang_section': return 'Idioma y textos'; + case 'settings.lang_title': return 'Idioma de la aplicación'; + case 'settings.lang_descr': return 'Idioma en el que se mostrarán los textos en la aplicación'; case 'settings.locale': return 'Región'; - case 'settings.locale_descr': return 'Establece el formato a usar para fechas, números...'; - case 'settings.locale_warn': return 'Al cambiar de región la app se refrescará'; + case 'settings.locale_descr': return 'Establecer el formato a utilizar para fechas, números...'; + case 'settings.locale_warn': return 'Al cambiar la región, la aplicación se actualizará'; case 'settings.first_day_of_week': return 'Primer día de la semana'; - case 'settings.general.display': return 'Ajustes generales'; - case 'settings.general.categories_descr': return 'Crea y edita categorías a tu gusto'; - case 'settings.general.other': return 'Ajustes avanzados'; - case 'settings.general.other_descr': return 'Otros ajustes generales de personalización de la app'; - case 'settings.general.appearance': return 'Apariencia'; - case 'settings.general.appearance_descr': return 'Tema de la aplicación, textos...'; - case 'settings.general.app_colors': return 'Colores de la aplicación'; - case 'settings.general.theme': return 'Tema'; - case 'settings.general.theme_auto': return 'Definido por el sistema'; - case 'settings.general.theme_light': return 'Claro'; - case 'settings.general.theme_dark': return 'Oscuro'; - case 'settings.general.amoled_mode': return 'Modo AMOLED'; - case 'settings.general.amoled_mode_descr': return 'Usa un fondo de pantalla negro puro cuando sea posible. Esto ayudará ligeramente a la bateria de dispositivos con pantallas AMOLED'; - case 'settings.general.dynamic_colors': return 'Colores dinámicos'; - case 'settings.general.dynamic_colors_descr': return 'Usa el color de acentuación de su sistema siempre que sea posible'; - case 'settings.general.accent_color': return 'Color de acentuación'; - case 'settings.general.accent_color_descr': return 'Elige el color que usará la app para enfatizar ciertas partes de la interfaz'; - case 'settings.general.prefer_calc': return 'Preferir calculadora'; - case 'settings.general.prefer_calc_descr': return 'Muestra en la página de adición/edición de transacciones una calculadora que simplificará las acciones en dispositivos sin teclado'; - case 'settings.data.display': return 'Datos'; - case 'settings.data.display_descr': return 'Exporta y importa tus datos para no perder nada'; - case 'settings.data.delete_all': return 'Eliminar mis datos'; - case 'settings.data.delete_all_header1': return 'Alto ahí padawan ⚠️⚠️'; - case 'settings.data.delete_all_message1': return '¿Estas seguro de que quieres continuar? Todos tus datos serán borrados permanentemente y no podrán ser recuperados'; - case 'settings.data.delete_all_header2': return 'Un último paso ⚠️⚠️'; - case 'settings.data.delete_all_message2': return 'Al eliminar una cuenta eliminarás todos tus datos personales almacenados. Tus cuentas, transacciones, presupuestos y categorías serán borrados y no podrán ser recuperados. ¿Estas de acuerdo?'; - case 'settings.about_us.display': return 'Información de la app'; - case 'settings.about_us.description': return 'Consulta los terminos y otra información relevante sobre Monekin. Ponte en contacto con la comunidad reportando errores, dejando sugerencias...'; - case 'settings.about_us.legal.display': return 'Información legal'; - case 'settings.about_us.legal.privacy': return 'Política de privacidad'; - case 'settings.about_us.legal.terms': return 'Términos de uso'; - case 'settings.about_us.legal.licenses': return 'Licencias'; - case 'settings.about_us.project.display': return 'Proyecto'; - case 'settings.about_us.project.contributors': return 'Colaboradores'; - case 'settings.about_us.project.contributors_descr': return 'Todos los desarrolladores que han hecho que Monekin crezca'; - case 'settings.about_us.project.contact': return 'Contacta con nosotros'; - case 'settings.help_us.display': return 'Ayúdanos'; - case 'settings.help_us.description': return 'Descubre de que formas puedes ayudar a que Monekin sea cada vez mejor'; - case 'settings.help_us.rate_us': return 'Califícanos'; - case 'settings.help_us.rate_us_descr': return '¡Cualquier valoración es bienvenida!'; - case 'settings.help_us.share': return 'Comparte Monekin'; - case 'settings.help_us.share_descr': return 'Comparte nuestra app a amigos y familiares'; - case 'settings.help_us.share_text': return 'Monekin! La mejor app de finanzas personales. Descargala aquí'; - case 'settings.help_us.thanks': return '¡Gracias!'; - case 'settings.help_us.thanks_long': return 'Tus contribuciones a Monekin y otros proyectos de código abierto, grandes o pequeños, hacen posibles grandes proyectos como este. Gracias por tomarse el tiempo para contribuir.'; - case 'settings.help_us.donate': return 'Haz una donación'; - case 'settings.help_us.donate_descr': return 'Con tu donación ayudaras a que la app siga recibiendo mejoras. ¿Que mejor forma que agradecer el trabajo realizado invitandome a un cafe?'; - case 'settings.help_us.donate_success': return 'Donación realizada. Muchas gracias por tu contribución! ❤️'; - case 'settings.help_us.donate_err': return 'Ups! Parece que ha habido un error a la hora de recibir tu pago'; - case 'settings.help_us.report': return 'Reporta errores, deja sugerencias...'; + case 'settings.theme_and_colors': return 'Tema y colores'; + case 'settings.theme': return 'Tema'; + case 'settings.theme_auto': return 'Definido por el sistema'; + case 'settings.theme_light': return 'Claro'; + case 'settings.theme_dark': return 'Oscuro'; + case 'settings.amoled_mode': return 'Modo AMOLED'; + case 'settings.amoled_mode_descr': return 'Usar un fondo negro puro cuando sea posible. Esto ayudará ligeramente a la batería de dispositivos con pantallas AMOLED'; + case 'settings.dynamic_colors': return 'Colores dinámicos'; + case 'settings.dynamic_colors_descr': return 'Usar el color de acento de su sistema siempre que sea posible'; + case 'settings.accent_color': return 'Color de acento'; + case 'settings.accent_color_descr': return 'Elegir el color que la aplicación usará para enfatizar ciertas partes de la interfaz'; + case 'more.title': return 'Más'; + case 'more.title_long': return 'Más acciones'; + case 'more.data.display': return 'Datos'; + case 'more.data.display_descr': return 'Exporta y importa tus datos para no perder nada'; + case 'more.data.delete_all': return 'Eliminar mis datos'; + case 'more.data.delete_all_header1': return 'Alto ahí padawan ⚠️⚠️'; + case 'more.data.delete_all_message1': return '¿Estas seguro de que quieres continuar? Todos tus datos serán borrados permanentemente y no podrán ser recuperados'; + case 'more.data.delete_all_header2': return 'Un último paso ⚠️⚠️'; + case 'more.data.delete_all_message2': return 'Al eliminar una cuenta eliminarás todos tus datos personales almacenados. Tus cuentas, transacciones, presupuestos y categorías serán borrados y no podrán ser recuperados. ¿Estas de acuerdo?'; + case 'more.about_us.display': return 'Información de la app'; + case 'more.about_us.description': return 'Consulta los terminos y otra información relevante sobre Monekin. Ponte en contacto con la comunidad reportando errores, dejando sugerencias...'; + case 'more.about_us.legal.display': return 'Información legal'; + case 'more.about_us.legal.privacy': return 'Política de privacidad'; + case 'more.about_us.legal.terms': return 'Términos de uso'; + case 'more.about_us.legal.licenses': return 'Licencias'; + case 'more.about_us.project.display': return 'Proyecto'; + case 'more.about_us.project.contributors': return 'Colaboradores'; + case 'more.about_us.project.contributors_descr': return 'Todos los desarrolladores que han hecho que Monekin crezca'; + case 'more.about_us.project.contact': return 'Contacta con nosotros'; + case 'more.help_us.display': return 'Ayúdanos'; + case 'more.help_us.description': return 'Descubre de que formas puedes ayudar a que Monekin sea cada vez mejor'; + case 'more.help_us.rate_us': return 'Califícanos'; + case 'more.help_us.rate_us_descr': return '¡Cualquier valoración es bienvenida!'; + case 'more.help_us.share': return 'Comparte Monekin'; + case 'more.help_us.share_descr': return 'Comparte nuestra app a amigos y familiares'; + case 'more.help_us.share_text': return 'Monekin! La mejor app de finanzas personales. Descargala aquí'; + case 'more.help_us.thanks': return '¡Gracias!'; + case 'more.help_us.thanks_long': return 'Tus contribuciones a Monekin y otros proyectos de código abierto, grandes o pequeños, hacen posibles grandes proyectos como este. Gracias por tomarse el tiempo para contribuir.'; + case 'more.help_us.donate': return 'Haz una donación'; + case 'more.help_us.donate_descr': return 'Con tu donación ayudaras a que la app siga recibiendo mejoras. ¿Que mejor forma que agradecer el trabajo realizado invitandome a un cafe?'; + case 'more.help_us.donate_success': return 'Donación realizada. Muchas gracias por tu contribución! ❤️'; + case 'more.help_us.donate_err': return 'Ups! Parece que ha habido un error a la hora de recibir tu pago'; + case 'more.help_us.report': return 'Reporta errores, deja sugerencias...'; case 'lang.es': return 'Español'; case 'lang.en': return 'Inglés'; default: return null; From 0afdcf724c3ca0819fcfb632445569dfab719352 Mon Sep 17 00:00:00 2001 From: enriqueloz88 Date: Thu, 15 Feb 2024 13:20:01 +0100 Subject: [PATCH 5/5] feat: Main layout redirections and styles --- lib/app/layout/navigation_sidebar.dart | 23 +++++-------- lib/app/layout/tabs.dart | 39 ++-------------------- lib/app/settings/backup_settings_page.dart | 11 ++++-- lib/core/routes/route_utils.dart | 27 ++++++++++++--- lib/main.dart | 7 ++++ 5 files changed, 49 insertions(+), 58 deletions(-) diff --git a/lib/app/layout/navigation_sidebar.dart b/lib/app/layout/navigation_sidebar.dart index ec51ae98..64022089 100644 --- a/lib/app/layout/navigation_sidebar.dart +++ b/lib/app/layout/navigation_sidebar.dart @@ -6,6 +6,7 @@ import 'package:monekin/core/presentation/responsive/breakpoint_container.dart'; import 'package:monekin/core/presentation/responsive/breakpoints.dart'; import 'package:monekin/core/presentation/widgets/user_avatar.dart'; import 'package:monekin/core/routes/destinations.dart'; +import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/main.dart'; double getNavigationSidebarWidth(BuildContext context) { @@ -52,6 +53,11 @@ class NavigationSidebarState extends State { final selectedNavItemIndex = menuItems .indexWhere((element) => element.id == selectedDestination!.id); + onDestinationSelected(int e) { + RouteUtils.popAllRoutesExceptFirst(); + tabsPageKey.currentState?.changePage(menuItems.elementAt(e)); + } + return AnimatedContainer( duration: const Duration(milliseconds: 1500), curve: Curves.easeInOutCubicEmphasized, @@ -84,27 +90,14 @@ class NavigationSidebarState extends State { destinations: menuItems .map((e) => e.toNavigationRailDestinationWidget()) .toList(), - onDestinationSelected: (e) => - tabsPageKey.currentState?.changePage(menuItems.elementAt(e)), + onDestinationSelected: onDestinationSelected, selectedIndex: selectedNavItemIndex < 0 ? null : selectedNavItemIndex), ), xlChild: SafeArea( child: HomeDrawer( drawerActions: menuItems, - onDestinationSelected: (e) { - // Pop all routes without animation: - navigatorKey.currentState!.pushAndRemoveUntil( - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const SizedBox(), - transitionDuration: const Duration(seconds: 0), - ), - (route) => route.isFirst); - navigatorKey.currentState!.pop(); - - tabsPageKey.currentState?.changePage(menuItems.elementAt(e)); - }, + onDestinationSelected: onDestinationSelected, selectedIndex: selectedNavItemIndex, ), ), diff --git a/lib/app/layout/tabs.dart b/lib/app/layout/tabs.dart index 49d7b834..121b1676 100644 --- a/lib/app/layout/tabs.dart +++ b/lib/app/layout/tabs.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:monekin/app/layout/lazy_indexed_stack.dart'; -import 'package:monekin/core/presentation/responsive/breakpoint_container.dart'; import 'package:monekin/core/presentation/responsive/breakpoints.dart'; import 'package:monekin/core/routes/destinations.dart'; import 'package:monekin/main.dart'; @@ -17,6 +16,8 @@ class TabsPageState extends State { MainMenuDestination? selectedDestination; void changePage(MainMenuDestination destination) { + print("HOLA"); + navigationSidebarKey.currentState?.setSelectedDestination(destination); setState(() { @@ -57,14 +58,7 @@ class TabsPageState extends State { index: allDestinations .indexWhere((element) => element.id == selectedDestination?.id), duration: const Duration(milliseconds: 300), - children: allDestinations - .map( - (e) => BreakpointContainer( - mdChild: _RightWidgetContainer(child: e.destination), - child: e.destination, - ), - ) - .toList(), + children: allDestinations.map((e) => e.destination).toList(), ); }), // selectedDestination?.destination ?? const SizedBox.shrink(), @@ -135,30 +129,3 @@ class FadeIndexedStackState extends State ); } } - -class _RightWidgetContainer extends StatelessWidget { - const _RightWidgetContainer({ - super.key, - required this.child, - }); - - final Widget child; - - @override - Widget build(BuildContext context) { - return Expanded( - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - ), - margin: const EdgeInsets.all(16), - child: Card( - elevation: 4, - clipBehavior: Clip.hardEdge, - child: child, - ), - ), - ); - } -} diff --git a/lib/app/settings/backup_settings_page.dart b/lib/app/settings/backup_settings_page.dart index b8d873aa..0c0ac069 100644 --- a/lib/app/settings/backup_settings_page.dart +++ b/lib/app/settings/backup_settings_page.dart @@ -8,9 +8,11 @@ import 'package:monekin/app/settings/settings.page.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/backup/backup_database_service.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; +import 'package:monekin/core/routes/destinations.dart'; import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/core/utils/number_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; +import 'package:monekin/main.dart'; class BackupSettingsPage extends StatelessWidget { const BackupSettingsPage({super.key}); @@ -53,8 +55,13 @@ class BackupSettingsPage extends StatelessWidget { return; } - // TODO: REPLACE ALL - // context.router.replaceAll([const MainLayoutRoute()]); + RouteUtils.popAllRoutesExceptFirst(); + + tabsPageKey.currentState!.changePage( + getAllDestinations(context, shortLabels: false) + .firstWhere((element) => + element.id == AppMenuDestinationsID.dashboard), + ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(t.backup.import.success)), diff --git a/lib/core/routes/route_utils.dart b/lib/core/routes/route_utils.dart index 57110422..b7ba2f7e 100644 --- a/lib/core/routes/route_utils.dart +++ b/lib/core/routes/route_utils.dart @@ -9,7 +9,17 @@ abstract class RouteUtils { }) { if (navigatorKey.currentState == null) return Future.value(null); - var pageRouteBuilder = PageRouteBuilder( + var pageRouteBuilder = getPageRouteBuilder(page); + + if (withReplacement) { + return navigatorKey.currentState!.pushReplacement(pageRouteBuilder); + } + + return navigatorKey.currentState!.push(pageRouteBuilder); + } + + static PageRouteBuilder getPageRouteBuilder(Widget page) { + return PageRouteBuilder( opaque: false, transitionDuration: const Duration(milliseconds: 300), reverseTransitionDuration: const Duration(milliseconds: 125), @@ -30,11 +40,18 @@ abstract class RouteUtils { return page; }, ); + } - if (withReplacement) { - return navigatorKey.currentState!.pushReplacement(pageRouteBuilder); - } + /// Pop all the routes in the stack except the first one without any animation + static void popAllRoutesExceptFirst() { + // This function can be useful when we want to return to the main layout page + navigatorKey.currentState!.pushAndRemoveUntil( + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => const SizedBox(), + transitionDuration: const Duration(seconds: 0), + ), + (route) => route.isFirst); - return navigatorKey.currentState!.push(pageRouteBuilder); + navigatorKey.currentState!.pop(); } } diff --git a/lib/main.dart b/lib/main.dart index 36d3df7e..1ff9af50 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:monekin/app/layout/tabs.dart'; import 'package:monekin/app/onboarding/intro.page.dart'; import 'package:monekin/core/database/services/app-data/app_data_service.dart'; import 'package:monekin/core/database/services/user-setting/user_setting_service.dart'; +import 'package:monekin/core/presentation/responsive/breakpoints.dart'; import 'package:monekin/core/presentation/theme.dart'; import 'package:monekin/core/utils/scroll_behavior_override.dart'; import 'package:monekin/i18n/translations.g.dart'; @@ -164,6 +165,12 @@ class MaterialAppContainer extends StatelessWidget { introSeen ? getNavigationSidebarWidth(context) : 0, color: Theme.of(context).canvasColor, ), + if (BreakPoint.of(context).isLargerThan(BreakpointID.sm)) + Container( + width: 2, + height: MediaQuery.of(context).size.height, + color: Theme.of(context).dividerColor, + ), Expanded(child: child ?? const SizedBox.shrink()), ], ),