From ffe1c115fab2154c7eed4a13a3eb7367db1c24c8 Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 18 Apr 2025 15:53:22 +0300 Subject: [PATCH] Cw 1038 filter transaction popup not scrollable (#2207) * ui:make overflowing filter sections scrollable * Update pull_request_template.md --- .github/pull_request_template.md | 1 + .../dashboard/widgets/filter_widget.dart | 245 +++++++++++------- lib/src/widgets/alert_close_button.dart | 30 +-- 3 files changed, 164 insertions(+), 112 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 18ad16e4b..272f7bbee 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,3 +11,4 @@ Please include a summary of the changes and which issue is fixed / feature is ad - [ ] Format code - [ ] Look for code duplication - [ ] Clear naming for variables and methods +- [ ] Manual tests in accessibility mode (TalkBack on Android) passed diff --git a/lib/src/screens/dashboard/widgets/filter_widget.dart b/lib/src/screens/dashboard/widgets/filter_widget.dart index eaf00a1de..81f29b81c 100644 --- a/lib/src/screens/dashboard/widgets/filter_widget.dart +++ b/lib/src/screens/dashboard/widgets/filter_widget.dart @@ -1,123 +1,178 @@ +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart'; -import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item.dart'; -import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/dashboard/filter_item.dart'; import 'package:flutter/material.dart'; -import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; - -//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; -class FilterWidget extends StatelessWidget { - FilterWidget({required this.filterItems}); +class FilterWidget extends StatefulWidget { + const FilterWidget({required this.filterItems, this.onClose, Key? key}) : super(key: key); final Map> filterItems; + final Function()? onClose; + + @override + _FilterWidgetState createState() => _FilterWidgetState(); +} + +class _FilterWidgetState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - const sectionDivider = const HorizontalSectionDivider(); - return PickerWrapperWidget( - children: [ - Padding( - padding: EdgeInsets.only(left: 24, right: 24, top: 24), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(24)), - child: Container( - color: Theme.of(context).extension()!.backgroundColor, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all(24.0), - child: Text( - S.of(context).filter_by, - style: TextStyle( - color: - Theme.of(context).extension()!.detailsTitlesColor, - fontSize: 16, - fontFamily: 'Lato', - decoration: TextDecoration.none, - ), - ), - ), - sectionDivider, - ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: filterItems.length, - separatorBuilder: (context, _) => sectionDivider, - itemBuilder: (_, index1) { - final title = filterItems.keys.elementAt(index1); - final section = filterItems.values.elementAt(index1); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 20, left: 24, right: 24), - child: Text( - title, - style: TextStyle( - color: Theme.of(context).extension()!.titleColor, - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none), - ), + return AlertBackground( + child: Column( + children: [ + const Expanded(child: SizedBox()), + Expanded( + flex: responsiveLayoutUtil.shouldRenderTabletUI ? 16 : 8, + child: LayoutBuilder( + builder: (context, constraints) { + double availableHeight = constraints.maxHeight; + return _buildFilterContent(context, availableHeight); + }, + ), + ), + Expanded( + child: AlertCloseButton( + key: const ValueKey('filter_wrapper_close_button_key'), + isPositioned: false, + onTap: widget.onClose, + ), + ), + const SizedBox(height: 24), + ], + ), + ); + } + + Widget _buildFilterContent(BuildContext context, double availableHeight) { + const sectionDivider = HorizontalSectionDivider(); + + const double totalHeaderHeight = 73; + const double filterTileMinHeight = 40; + double availableHeightForItems = availableHeight - totalHeaderHeight; + + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Container( + color: Theme.of(context).extension()!.backgroundColor, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(24.0), + child: Text( + S.of(context).filter_by, + style: TextStyle( + color: Theme.of(context) + .extension()! + .detailsTitlesColor, + fontSize: 16, + fontFamily: 'Lato', + decoration: TextDecoration.none, ), - ListView.builder( - padding: EdgeInsets.symmetric(horizontal: 28.0), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + ), + ), + sectionDivider, + ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.filterItems.length, + separatorBuilder: (context, _) => sectionDivider, + itemBuilder: (_, index1) { + final title = widget.filterItems.keys.elementAt(index1); + final section = widget.filterItems.values.elementAt(index1); + + final double itemHeight = + availableHeightForItems / widget.filterItems.length; + + final isSectionScrollable = + (itemHeight < (section.length * filterTileMinHeight)); + + final Widget sectionListView = ListView.builder( + controller: isSectionScrollable ? _scrollController : null, + padding: const EdgeInsets.symmetric(horizontal: 28.0), + shrinkWrap: isSectionScrollable ? false : true, + physics: isSectionScrollable + ? const BouncingScrollPhysics() + : const NeverScrollableScrollPhysics(), itemCount: section.length, itemBuilder: (_, index2) { final item = section[index2]; - - if (item is DropdownFilterItem) { - return Padding( - padding: EdgeInsets.fromLTRB(8, 0, 8, 16), - child: Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 1.0, - color: Theme.of(context).extension()!.secondaryTextColor), - ), - ), - child: DropdownFilterList( - items: item.items, - caption: item.caption, - selectedItem: item.selectedItem, - onItemSelected: item.onItemSelected, - ), - ), - ); - } final content = Observer( - builder: (_) => StandardCheckbox( - value: item.value(), - caption: item.caption, - gradientBackground: true, - borderColor: Theme.of(context).dividerColor, - iconColor: Colors.white, - onChanged: (value) => item.onChanged(), - )); - return FilterTile(child: content); + builder: (_) => StandardCheckbox( + value: item.value(), + caption: item.caption, + gradientBackground: true, + borderColor: Theme.of(context).dividerColor, + iconColor: Colors.white, + onChanged: (value) => item.onChanged(), + ), + ); + return FilterTile( + child: content, + ); }, - ) - ], - ); - }, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 20, left: 24, right: 24), + child: Text( + title, + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + ), + ), + ), + Container( + height: isSectionScrollable ? itemHeight - totalHeaderHeight : null, + child: isSectionScrollable + ? Scrollbar( + controller: _scrollController, + thumbVisibility: true, + child: sectionListView, + ) + : sectionListView, + ), + ], + ); + }, + ), + ], ), - ]), + ), ), ), - ) - ], + ], + ), ); } } diff --git a/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index 6ef0bdaa5..30e37ef20 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -7,6 +7,7 @@ class AlertCloseButton extends StatelessWidget { this.image, this.bottom, this.onTap, + this.isPositioned = true, super.key, }); @@ -14,6 +15,7 @@ class AlertCloseButton extends StatelessWidget { final Image? image; final double? bottom; + final bool isPositioned; final closeButton = Image.asset( 'assets/images/close.png', @@ -22,24 +24,18 @@ class AlertCloseButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Positioned( - bottom: bottom ?? 60, - child: GestureDetector( + final button = GestureDetector( onTap: onTap ?? () => Navigator.of(context).pop(), child: Semantics( - label: S.of(context).close, - button: true, - enabled: true, - child: Container( - height: 42, - width: 42, - decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), - child: Center( - child: image ?? closeButton, - ), - ), - ), - ), - ); + label: S.of(context).close, + button: true, + enabled: true, + child: Container( + height: 42, + width: 42, + decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle), + child: Center(child: image ?? closeButton)))); + + return isPositioned ? Positioned(bottom: bottom ?? 60, child: button) : button; } }