diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index 7d769961..6a152d6e 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:monekin/app/accounts/account_form.dart'; import 'package:monekin/app/accounts/details/account_details.dart'; @@ -42,11 +43,25 @@ class _DashboardPageState extends State { final ScrollController _scrollController = ScrollController(); bool showSmallHeader = false; + bool isFloatingButtonExtended = true; + @override void initState() { super.initState(); - _scrollController.addListener(_setSmallHeaderVisible); + _scrollController.addListener(() { + _setSmallHeaderVisible(); + + bool shouldExtendButton = _scrollController.offset <= 10 || + _scrollController.position.userScrollDirection != + ScrollDirection.reverse; + + if (isFloatingButtonExtended != shouldExtendButton) { + setState(() { + isFloatingButtonExtended = shouldExtendButton; + }); + } + }); } @override @@ -82,8 +97,9 @@ class _DashboardPageState extends State { return Scaffold( appBar: EmptyAppBar( color: Theme.of(context).colorSchemeExtended.dashboardHeader), - floatingActionButton: - hideDrawerAndFloatingButton ? null : const NewTransactionButton(), + floatingActionButton: hideDrawerAndFloatingButton + ? null + : NewTransactionButton(isExtended: isFloatingButtonExtended), drawer: hideDrawerAndFloatingButton ? null : Drawer( diff --git a/lib/app/home/widgets/new_transaction_fl_button.dart b/lib/app/home/widgets/new_transaction_fl_button.dart index 1ad52114..00bf90f5 100644 --- a/lib/app/home/widgets/new_transaction_fl_button.dart +++ b/lib/app/home/widgets/new_transaction_fl_button.dart @@ -2,12 +2,16 @@ import 'package:flutter/material.dart'; import 'package:monekin/app/accounts/account_form.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/animations/animated_expanded.dart'; import 'package:monekin/core/presentation/widgets/confirm_dialog.dart'; import 'package:monekin/core/routes/route_utils.dart'; import 'package:monekin/i18n/translations.g.dart'; class NewTransactionButton extends StatelessWidget { - const NewTransactionButton({super.key, this.isExtended = false}); + const NewTransactionButton({ + super.key, + this.isExtended = true, + }); final bool isExtended; @@ -22,7 +26,7 @@ class NewTransactionButton extends StatelessWidget { ).then((value) { if (value != true) return; - RouteUtils.pushRoute(context, AccountFormPage()); + RouteUtils.pushRoute(context, const AccountFormPage()); }); } @@ -41,20 +45,20 @@ class NewTransactionButton extends StatelessWidget { @override 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), - ); - } - - return FloatingActionButton( - heroTag: 'new-transaction-floating-button', - tooltip: t.transaction.create, + final t = Translations.of(context); + + return FloatingActionButton.extended( + heroTag: null, onPressed: () => _onPressed(context), - child: const Icon(Icons.add_rounded), + icon: const Icon(Icons.add_rounded), + extendedPadding: const EdgeInsetsDirectional.only(start: 16, end: 16), + extendedIconLabelSpacing: isExtended ? 8 : 0, + label: AnimatedExpanded( + duration: const Duration(milliseconds: 250), + expand: isExtended, + axis: Axis.horizontal, + child: Text(t.transaction.create), + ), ); } } diff --git a/lib/app/layout/navigation_sidebar.dart b/lib/app/layout/navigation_sidebar.dart index 662991a0..51ebc948 100644 --- a/lib/app/layout/navigation_sidebar.dart +++ b/lib/app/layout/navigation_sidebar.dart @@ -68,7 +68,7 @@ class NavigationSidebarState extends State { leading: const Column( children: [ SizedBox(height: 16), - NewTransactionButton(), + NewTransactionButton(isExtended: false), SizedBox(height: 16), ], ), diff --git a/lib/app/transactions/transactions.page.dart b/lib/app/transactions/transactions.page.dart index 087bff26..1dade9a7 100644 --- a/lib/app/transactions/transactions.page.dart +++ b/lib/app/transactions/transactions.page.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:monekin/app/home/widgets/new_transaction_fl_button.dart'; import 'package:monekin/app/layout/tabs.dart'; @@ -36,6 +37,8 @@ class _TransactionsPageState extends State { FocusNode searchFocusNode = FocusNode(); final searchController = TextEditingController(); + bool isFloatingButtonExtended = true; + List selectedTransactions = []; @override @@ -143,7 +146,8 @@ class _TransactionsPageState extends State { icon: const Icon(Icons.filter_alt_outlined)), ], ), - floatingActionButton: const NewTransactionButton(isExtended: true), + floatingActionButton: + NewTransactionButton(isExtended: isFloatingButtonExtended), body: Column( children: [ if (filters.hasFilter) ...[ @@ -250,6 +254,17 @@ class _TransactionsPageState extends State { selectedTransactions = [tr]; }); }, + onScrollChange: (controller) { + bool shouldExtendButton = controller.offset <= 10 || + controller.position.userScrollDirection != + ScrollDirection.reverse; + + if (isFloatingButtonExtended != shouldExtendButton) { + setState(() { + isFloatingButtonExtended = shouldExtendButton; + }); + } + }, onTap: selectedTransactions.isEmpty ? null : toggleTransaction, onEmptyList: NoResults( title: filters.hasFilter ? null : t.general.empty_warn, diff --git a/lib/app/transactions/widgets/transaction_list.dart b/lib/app/transactions/widgets/transaction_list.dart index 7035f224..392228c1 100644 --- a/lib/app/transactions/widgets/transaction_list.dart +++ b/lib/app/transactions/widgets/transaction_list.dart @@ -30,6 +30,7 @@ class TransactionListComponent extends StatefulWidget { this.onTap, this.selectedTransactions = const [], this.onTransactionsLoaded, + this.onScrollChange, }); final TransactionFilters filters; @@ -52,6 +53,8 @@ class TransactionListComponent extends StatefulWidget { final Object? Function(MoneyTransaction tr)? heroTagBuilder; + final void Function(ScrollController controller)? onScrollChange; + /// Action to trigger when a transaction tile is long pressed. If `null`, /// the tile will display a modal with some quick actions for /// this transaction @@ -72,21 +75,24 @@ class TransactionListComponent extends StatefulWidget { } class _TransactionListComponentState extends State { - ScrollController listController = ScrollController(); + ScrollController listScrollController = ScrollController(); int currentPage = 1; + bool isEnabled = true; @override void initState() { super.initState(); - listController.addListener(() { - if (listController.offset >= listController.position.maxScrollExtent && - !listController.position.outOfRange) { + listScrollController.addListener(() { + if (listScrollController.offset >= + listScrollController.position.maxScrollExtent && + !listScrollController.position.outOfRange) { currentPage += 1; - setState(() {}); } + + widget.onScrollChange?.call(listScrollController); }); } @@ -158,7 +164,7 @@ class _TransactionListComponentState extends State { return ListView.separated( physics: const BouncingScrollPhysics(), itemCount: transactions.length + 1, - controller: listController, + controller: listScrollController, shrinkWrap: true, itemBuilder: (context, index) { if (transactions.isEmpty) return Container();