From c0009061780952c3585d6d5b55274c36961402c7 Mon Sep 17 00:00:00 2001 From: ARYPROGRAMMER Date: Thu, 19 Dec 2024 00:32:42 +0530 Subject: [PATCH 1/6] feat: changed transaction floating button behaviour --- lib/app/home/dashboard.page.dart | 19 ++++++++-- .../widgets/new_transaction_fl_button.dart | 38 +++++++++++-------- lib/app/transactions/transactions.page.dart | 8 +++- .../widgets/transaction_list.dart | 21 +++++++++- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index e6174aea..96a95c67 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -49,12 +49,24 @@ class _DashboardPageState extends State { DatePeriodState dateRangeService = const DatePeriodState(); final ScrollController _scrollController = ScrollController(); bool showSmallHeader = false; + bool isEnabled = false; @override void initState() { super.initState(); - _scrollController.addListener(_setSmallHeaderVisible); + _scrollController.addListener(() { + _setSmallHeaderVisible; + if (_scrollController.offset > 10) { + setState(() { + isEnabled = true; + }); + } else { + setState(() { + isEnabled = false; + }); + } + }); } @override @@ -83,8 +95,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: isEnabled), 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..85a1f58b 100644 --- a/lib/app/home/widgets/new_transaction_fl_button.dart +++ b/lib/app/home/widgets/new_transaction_fl_button.dart @@ -7,7 +7,7 @@ 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; @@ -41,20 +41,28 @@ 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, - onPressed: () => _onPressed(context), - child: const Icon(Icons.add_rounded), + final t = Translations.of(context); + + return Material( + color: Colors.transparent, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + child: FloatingActionButton.extended( + heroTag: 'new-transaction-floating-button', + onPressed: () => _onPressed(context), + isExtended: !isExtended, + icon: const Icon(Icons.add_rounded), + extendedIconLabelSpacing: 8.0, + label: AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.linear, + child: !isExtended + ? Text(t.transaction.create) + : const SizedBox.shrink(), + ), + ), + ), ); } } diff --git a/lib/app/transactions/transactions.page.dart b/lib/app/transactions/transactions.page.dart index 087bff26..f46a0148 100644 --- a/lib/app/transactions/transactions.page.dart +++ b/lib/app/transactions/transactions.page.dart @@ -35,6 +35,7 @@ class _TransactionsPageState extends State { bool searchActive = false; FocusNode searchFocusNode = FocusNode(); final searchController = TextEditingController(); + bool isEnabled = false; List selectedTransactions = []; @@ -143,7 +144,7 @@ class _TransactionsPageState extends State { icon: const Icon(Icons.filter_alt_outlined)), ], ), - floatingActionButton: const NewTransactionButton(isExtended: true), + floatingActionButton: NewTransactionButton(isExtended: isEnabled), body: Column( children: [ if (filters.hasFilter) ...[ @@ -250,6 +251,11 @@ class _TransactionsPageState extends State { selectedTransactions = [tr]; }); }, + onScrollChange: (newIsEnabled) { + setState(() { + isEnabled = newIsEnabled; + }); + }, 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..38efdfdb 100644 --- a/lib/app/transactions/widgets/transaction_list.dart +++ b/lib/app/transactions/widgets/transaction_list.dart @@ -29,7 +29,7 @@ class TransactionListComponent extends StatefulWidget { this.onLongPress, this.onTap, this.selectedTransactions = const [], - this.onTransactionsLoaded, + this.onTransactionsLoaded, this.onScrollChange, }); final TransactionFilters filters; @@ -52,6 +52,8 @@ class TransactionListComponent extends StatefulWidget { final Object? Function(MoneyTransaction tr)? heroTagBuilder; + final void Function(bool isEnabled)? 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 @@ -75,6 +77,7 @@ class _TransactionListComponentState extends State { ScrollController listController = ScrollController(); int currentPage = 1; + bool isEnabled = true; @override void initState() { @@ -84,9 +87,23 @@ class _TransactionListComponentState extends State { if (listController.offset >= listController.position.maxScrollExtent && !listController.position.outOfRange) { currentPage += 1; - setState(() {}); } + if (listController.offset > 10) { + if (!isEnabled) { + setState(() { + isEnabled = true; + }); + widget.onScrollChange?.call(isEnabled); // Notify parent + } + } else { + if (isEnabled) { + setState(() { + isEnabled = false; + }); + widget.onScrollChange?.call(isEnabled); // Notify parent + } + } }); } From 6ea376da6c18224df435af8324895145bbdd6074 Mon Sep 17 00:00:00 2001 From: Enrique Lozano Cebriano <61509169+enrique-lozano@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:56:06 +0100 Subject: [PATCH 2/6] fix: Floating action button animation not firing --- .../widgets/new_transaction_fl_button.dart | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/lib/app/home/widgets/new_transaction_fl_button.dart b/lib/app/home/widgets/new_transaction_fl_button.dart index 85a1f58b..b425e2cc 100644 --- a/lib/app/home/widgets/new_transaction_fl_button.dart +++ b/lib/app/home/widgets/new_transaction_fl_button.dart @@ -2,6 +2,7 @@ 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'; @@ -43,25 +44,15 @@ class NewTransactionButton extends StatelessWidget { Widget build(BuildContext context) { final t = Translations.of(context); - return Material( - color: Colors.transparent, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.linear, - child: FloatingActionButton.extended( - heroTag: 'new-transaction-floating-button', - onPressed: () => _onPressed(context), - isExtended: !isExtended, - icon: const Icon(Icons.add_rounded), - extendedIconLabelSpacing: 8.0, - label: AnimatedSize( - duration: const Duration(milliseconds: 300), - curve: Curves.linear, - child: !isExtended - ? Text(t.transaction.create) - : const SizedBox.shrink(), - ), - ), + return FloatingActionButton.extended( + heroTag: 'new-transaction-floating-button', + onPressed: () => _onPressed(context), + icon: const Icon(Icons.add_rounded), + extendedIconLabelSpacing: !isExtended ? 8 : 0, + label: AnimatedExpanded( + expand: !isExtended, + axis: Axis.horizontal, + child: Text(t.transaction.create), ), ); } From b160d1fb68df4f93b8cc00f2227fd69aea4d21eb Mon Sep 17 00:00:00 2001 From: Enrique Lozano Cebriano <61509169+enrique-lozano@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:38:54 +0100 Subject: [PATCH 3/6] feat: Change floating button behaviour --- lib/app/home/dashboard.page.dart | 19 +++++++---- .../widgets/new_transaction_fl_button.dart | 14 +++++--- lib/app/layout/navigation_sidebar.dart | 2 +- lib/app/transactions/transactions.page.dart | 23 +++++++++---- .../widgets/transaction_list.dart | 33 +++++++------------ 5 files changed, 50 insertions(+), 41 deletions(-) diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index 4df8d2a7..e9d63c59 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'; @@ -41,7 +42,8 @@ class _DashboardPageState extends State { DatePeriodState dateRangeService = const DatePeriodState(); final ScrollController _scrollController = ScrollController(); bool showSmallHeader = false; - bool isEnabled = false; + + bool isFloatingButtonExtended = true; @override void initState() { @@ -49,13 +51,16 @@ class _DashboardPageState extends State { _scrollController.addListener(() { _setSmallHeaderVisible; - if (_scrollController.offset > 10) { + + if (_scrollController.offset > 10 && + _scrollController.position.userScrollDirection == + ScrollDirection.reverse) { setState(() { - isEnabled = true; + isFloatingButtonExtended = false; }); } else { setState(() { - isEnabled = false; + isFloatingButtonExtended = true; }); } }); @@ -92,11 +97,11 @@ class _DashboardPageState extends State { BreakPoint.of(context).isLargerOrEqualTo(BreakpointID.md); return Scaffold( - appBar: EmptyAppBar( color: Theme.of(context).colorSchemeExtended.dashboardHeader), - floatingActionButton: - hideDrawerAndFloatingButton ? null : NewTransactionButton(isExtended: isEnabled), + 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 b425e2cc..886d86b8 100644 --- a/lib/app/home/widgets/new_transaction_fl_button.dart +++ b/lib/app/home/widgets/new_transaction_fl_button.dart @@ -8,7 +8,10 @@ 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 = true}); + const NewTransactionButton({ + super.key, + this.isExtended = true, + }); final bool isExtended; @@ -23,7 +26,7 @@ class NewTransactionButton extends StatelessWidget { ).then((value) { if (value != true) return; - RouteUtils.pushRoute(context, AccountFormPage()); + RouteUtils.pushRoute(context, const AccountFormPage()); }); } @@ -45,12 +48,13 @@ class NewTransactionButton extends StatelessWidget { final t = Translations.of(context); return FloatingActionButton.extended( - heroTag: 'new-transaction-floating-button', + heroTag: null, onPressed: () => _onPressed(context), icon: const Icon(Icons.add_rounded), - extendedIconLabelSpacing: !isExtended ? 8 : 0, + extendedIconLabelSpacing: isExtended ? 8 : 0, label: AnimatedExpanded( - expand: !isExtended, + 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 f46a0148..c1c30979 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'; @@ -35,7 +36,8 @@ class _TransactionsPageState extends State { bool searchActive = false; FocusNode searchFocusNode = FocusNode(); final searchController = TextEditingController(); - bool isEnabled = false; + + bool isFloatingButtonExtended = true; List selectedTransactions = []; @@ -144,7 +146,8 @@ class _TransactionsPageState extends State { icon: const Icon(Icons.filter_alt_outlined)), ], ), - floatingActionButton: NewTransactionButton(isExtended: isEnabled), + floatingActionButton: + NewTransactionButton(isExtended: isFloatingButtonExtended), body: Column( children: [ if (filters.hasFilter) ...[ @@ -251,10 +254,18 @@ class _TransactionsPageState extends State { selectedTransactions = [tr]; }); }, - onScrollChange: (newIsEnabled) { - setState(() { - isEnabled = newIsEnabled; - }); + onScrollChange: (controller) { + if (controller.offset > 10 && + controller.position.userScrollDirection == + ScrollDirection.reverse) { + setState(() { + isFloatingButtonExtended = false; + }); + } else { + setState(() { + isFloatingButtonExtended = true; + }); + } }, onTap: selectedTransactions.isEmpty ? null : toggleTransaction, onEmptyList: NoResults( diff --git a/lib/app/transactions/widgets/transaction_list.dart b/lib/app/transactions/widgets/transaction_list.dart index 38efdfdb..392228c1 100644 --- a/lib/app/transactions/widgets/transaction_list.dart +++ b/lib/app/transactions/widgets/transaction_list.dart @@ -29,7 +29,8 @@ class TransactionListComponent extends StatefulWidget { this.onLongPress, this.onTap, this.selectedTransactions = const [], - this.onTransactionsLoaded, this.onScrollChange, + this.onTransactionsLoaded, + this.onScrollChange, }); final TransactionFilters filters; @@ -52,7 +53,7 @@ class TransactionListComponent extends StatefulWidget { final Object? Function(MoneyTransaction tr)? heroTagBuilder; - final void Function(bool isEnabled)? onScrollChange; + 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 @@ -74,7 +75,7 @@ class TransactionListComponent extends StatefulWidget { } class _TransactionListComponentState extends State { - ScrollController listController = ScrollController(); + ScrollController listScrollController = ScrollController(); int currentPage = 1; bool isEnabled = true; @@ -83,27 +84,15 @@ class _TransactionListComponentState extends State { 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(() {}); } - if (listController.offset > 10) { - if (!isEnabled) { - setState(() { - isEnabled = true; - }); - widget.onScrollChange?.call(isEnabled); // Notify parent - } - } else { - if (isEnabled) { - setState(() { - isEnabled = false; - }); - widget.onScrollChange?.call(isEnabled); // Notify parent - } - } + + widget.onScrollChange?.call(listScrollController); }); } @@ -175,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(); From e5b8605649232fc861a4175485d683e0f3c6fdfa Mon Sep 17 00:00:00 2001 From: Enrique Lozano Cebriano <61509169+enrique-lozano@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:41:48 +0100 Subject: [PATCH 4/6] fix: Small header not showing in the dashboard after scroll --- lib/app/home/dashboard.page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index e9d63c59..02072576 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -50,7 +50,7 @@ class _DashboardPageState extends State { super.initState(); _scrollController.addListener(() { - _setSmallHeaderVisible; + _setSmallHeaderVisible(); if (_scrollController.offset > 10 && _scrollController.position.userScrollDirection == From feda9d01757d7fd0a37dddfb0b10de28b037e80b Mon Sep 17 00:00:00 2001 From: Enrique Lozano Cebriano <61509169+enrique-lozano@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:10:02 +0100 Subject: [PATCH 5/6] fix(ux/ui): Floating button padding --- lib/app/home/widgets/new_transaction_fl_button.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/app/home/widgets/new_transaction_fl_button.dart b/lib/app/home/widgets/new_transaction_fl_button.dart index 886d86b8..00bf90f5 100644 --- a/lib/app/home/widgets/new_transaction_fl_button.dart +++ b/lib/app/home/widgets/new_transaction_fl_button.dart @@ -51,6 +51,7 @@ class NewTransactionButton extends StatelessWidget { heroTag: null, onPressed: () => _onPressed(context), 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), From b819a412bd8c64e7703c8cbbee12221756ffaa57 Mon Sep 17 00:00:00 2001 From: Enrique Lozano Cebriano <61509169+enrique-lozano@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:16:57 +0100 Subject: [PATCH 6/6] perf: Reduce the number of reloads on scroll --- lib/app/home/dashboard.page.dart | 14 ++++++-------- lib/app/transactions/transactions.page.dart | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/app/home/dashboard.page.dart b/lib/app/home/dashboard.page.dart index 02072576..6a152d6e 100644 --- a/lib/app/home/dashboard.page.dart +++ b/lib/app/home/dashboard.page.dart @@ -52,15 +52,13 @@ class _DashboardPageState extends State { _scrollController.addListener(() { _setSmallHeaderVisible(); - if (_scrollController.offset > 10 && - _scrollController.position.userScrollDirection == - ScrollDirection.reverse) { - setState(() { - isFloatingButtonExtended = false; - }); - } else { + bool shouldExtendButton = _scrollController.offset <= 10 || + _scrollController.position.userScrollDirection != + ScrollDirection.reverse; + + if (isFloatingButtonExtended != shouldExtendButton) { setState(() { - isFloatingButtonExtended = true; + isFloatingButtonExtended = shouldExtendButton; }); } }); diff --git a/lib/app/transactions/transactions.page.dart b/lib/app/transactions/transactions.page.dart index c1c30979..1dade9a7 100644 --- a/lib/app/transactions/transactions.page.dart +++ b/lib/app/transactions/transactions.page.dart @@ -255,15 +255,13 @@ class _TransactionsPageState extends State { }); }, onScrollChange: (controller) { - if (controller.offset > 10 && - controller.position.userScrollDirection == - ScrollDirection.reverse) { - setState(() { - isFloatingButtonExtended = false; - }); - } else { + bool shouldExtendButton = controller.offset <= 10 || + controller.position.userScrollDirection != + ScrollDirection.reverse; + + if (isFloatingButtonExtended != shouldExtendButton) { setState(() { - isFloatingButtonExtended = true; + isFloatingButtonExtended = shouldExtendButton; }); } },