Cw 1038 filter transaction popup not scrollable (#2207)
* ui:make overflowing filter sections scrollable * Update pull_request_template.md
Serhii committed
Apr 18, 2025 at 15:53 UTC
ffe1c115fab2154c7eed4a13a3eb7367db1c24c8
3 files changed
+164
-112
.github/pull_request_template.md
+1
@@ -11,3 +11,4 @@ Please include a summary of the changes and which issue is fixed / feature is ad
11
- [ ] Format code
12
- [ ] Look for code duplication
13
- [ ] Clear naming for variables and methods
14
+- [ ] Manual tests in accessibility mode (TalkBack on Android) passed
lib/src/screens/dashboard/widgets/filter_widget.dart
+150
-95
@@ -1,123 +1,178 @@
1
+import 'package:cake_wallet/src/widgets/alert_background.dart';
2
+import 'package:cake_wallet/src/widgets/alert_close_button.dart';
3
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
4
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart';
5
import 'package:cake_wallet/src/widgets/section_divider.dart';
6
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
7
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
6
-import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item.dart';
7
-import 'package:cake_wallet/view_model/dashboard/dropdown_filter_item_widget.dart';
8
+import 'package:cake_wallet/utils/responsive_layout_util.dart';
9
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
10
import 'package:flutter/material.dart';
10
-import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
11
import 'package:cake_wallet/generated/i18n.dart';
12
import 'package:flutter_mobx/flutter_mobx.dart';
13
-
14
-//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
13
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
14
17
-class FilterWidget extends StatelessWidget {
18
- FilterWidget({required this.filterItems});
15
+class FilterWidget extends StatefulWidget {
16
+ const FilterWidget({required this.filterItems, this.onClose, Key? key}) : super(key: key);
17
18
final Map<String, List<FilterItem>> filterItems;
19
+ final Function()? onClose;
20
+
21
+ @override
22
+ _FilterWidgetState createState() => _FilterWidgetState();
23
+}
24
+
25
+class _FilterWidgetState extends State<FilterWidget> {
26
+ final ScrollController _scrollController = ScrollController();
27
+
28
+ @override
29
+ void dispose() {
30
+ _scrollController.dispose();
31
+ super.dispose();
32
+ }
33
34
@override
35
Widget build(BuildContext context) {
24
- const sectionDivider = const HorizontalSectionDivider();
25
- return PickerWrapperWidget(
26
- children: [
27
- Padding(
28
- padding: EdgeInsets.only(left: 24, right: 24, top: 24),
29
- child: ClipRRect(
30
- borderRadius: BorderRadius.all(Radius.circular(24)),
31
- child: Container(
32
- color: Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor,
33
- child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
34
- Padding(
35
- padding: EdgeInsets.all(24.0),
36
- child: Text(
37
- S.of(context).filter_by,
38
- style: TextStyle(
39
- color:
40
- Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
41
- fontSize: 16,
42
- fontFamily: 'Lato',
43
- decoration: TextDecoration.none,
44
- ),
45
- ),
46
- ),
47
- sectionDivider,
48
- ListView.separated(
49
- padding: EdgeInsets.zero,
50
- shrinkWrap: true,
51
- physics: const NeverScrollableScrollPhysics(),
52
- itemCount: filterItems.length,
53
- separatorBuilder: (context, _) => sectionDivider,
54
- itemBuilder: (_, index1) {
55
- final title = filterItems.keys.elementAt(index1);
56
- final section = filterItems.values.elementAt(index1);
57
- return Column(
58
- crossAxisAlignment: CrossAxisAlignment.start,
59
- children: <Widget>[
60
- Padding(
61
- padding: EdgeInsets.only(top: 20, left: 24, right: 24),
62
- child: Text(
63
- title,
64
- style: TextStyle(
65
- color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
66
- fontSize: 16,
67
- fontFamily: 'Lato',
68
- fontWeight: FontWeight.bold,
69
- decoration: TextDecoration.none),
70
- ),
36
+ return AlertBackground(
37
+ child: Column(
38
+ children: [
39
+ const Expanded(child: SizedBox()),
40
+ Expanded(
41
+ flex: responsiveLayoutUtil.shouldRenderTabletUI ? 16 : 8,
42
+ child: LayoutBuilder(
43
+ builder: (context, constraints) {
44
+ double availableHeight = constraints.maxHeight;
45
+ return _buildFilterContent(context, availableHeight);
46
+ },
47
+ ),
48
+ ),
49
+ Expanded(
50
+ child: AlertCloseButton(
51
+ key: const ValueKey('filter_wrapper_close_button_key'),
52
+ isPositioned: false,
53
+ onTap: widget.onClose,
54
+ ),
55
+ ),
56
+ const SizedBox(height: 24),
57
+ ],
58
+ ),
59
+ );
60
+ }
61
+
62
+ Widget _buildFilterContent(BuildContext context, double availableHeight) {
63
+ const sectionDivider = HorizontalSectionDivider();
64
+
65
+ const double totalHeaderHeight = 73;
66
+ const double filterTileMinHeight = 40;
67
+ double availableHeightForItems = availableHeight - totalHeaderHeight;
68
+
69
+ return Center(
70
+ child: Column(
71
+ mainAxisSize: MainAxisSize.min,
72
+ children: [
73
+ Padding(
74
+ padding: const EdgeInsets.only(left: 24, right: 24, top: 24),
75
+ child: ClipRRect(
76
+ borderRadius: BorderRadius.circular(24),
77
+ child: Container(
78
+ color: Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor,
79
+ child: Column(
80
+ crossAxisAlignment: CrossAxisAlignment.start,
81
+ children: [
82
+ Padding(
83
+ padding: const EdgeInsets.all(24.0),
84
+ child: Text(
85
+ S.of(context).filter_by,
86
+ style: TextStyle(
87
+ color: Theme.of(context)
88
+ .extension<TransactionTradeTheme>()!
89
+ .detailsTitlesColor,
90
+ fontSize: 16,
91
+ fontFamily: 'Lato',
92
+ decoration: TextDecoration.none,
93
),
72
- ListView.builder(
73
- padding: EdgeInsets.symmetric(horizontal: 28.0),
74
- shrinkWrap: true,
75
- physics: const NeverScrollableScrollPhysics(),
94
+ ),
95
+ ),
96
+ sectionDivider,
97
+ ListView.separated(
98
+ padding: EdgeInsets.zero,
99
+ shrinkWrap: true,
100
+ physics: const NeverScrollableScrollPhysics(),
101
+ itemCount: widget.filterItems.length,
102
+ separatorBuilder: (context, _) => sectionDivider,
103
+ itemBuilder: (_, index1) {
104
+ final title = widget.filterItems.keys.elementAt(index1);
105
+ final section = widget.filterItems.values.elementAt(index1);
106
+
107
+ final double itemHeight =
108
+ availableHeightForItems / widget.filterItems.length;
109
+
110
+ final isSectionScrollable =
111
+ (itemHeight < (section.length * filterTileMinHeight));
112
+
113
+ final Widget sectionListView = ListView.builder(
114
+ controller: isSectionScrollable ? _scrollController : null,
115
+ padding: const EdgeInsets.symmetric(horizontal: 28.0),
116
+ shrinkWrap: isSectionScrollable ? false : true,
117
+ physics: isSectionScrollable
118
+ ? const BouncingScrollPhysics()
119
+ : const NeverScrollableScrollPhysics(),
120
itemCount: section.length,
121
itemBuilder: (_, index2) {
122
final item = section[index2];
79
-
80
- if (item is DropdownFilterItem) {
81
- return Padding(
82
- padding: EdgeInsets.fromLTRB(8, 0, 8, 16),
83
- child: Container(
84
- decoration: BoxDecoration(
85
- border: Border(
86
- bottom: BorderSide(
87
- width: 1.0,
88
- color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
89
- ),
90
- ),
91
- child: DropdownFilterList(
92
- items: item.items,
93
- caption: item.caption,
94
- selectedItem: item.selectedItem,
95
- onItemSelected: item.onItemSelected,
96
- ),
97
- ),
98
- );
99
- }
123
final content = Observer(
101
- builder: (_) => StandardCheckbox(
102
- value: item.value(),
103
- caption: item.caption,
104
- gradientBackground: true,
105
- borderColor: Theme.of(context).dividerColor,
106
- iconColor: Colors.white,
107
- onChanged: (value) => item.onChanged(),
108
- ));
109
- return FilterTile(child: content);
124
+ builder: (_) => StandardCheckbox(
125
+ value: item.value(),
126
+ caption: item.caption,
127
+ gradientBackground: true,
128
+ borderColor: Theme.of(context).dividerColor,
129
+ iconColor: Colors.white,
130
+ onChanged: (value) => item.onChanged(),
131
+ ),
132
+ );
133
+ return FilterTile(
134
+ child: content,
135
+ );
136
},
111
- )
112
- ],
113
- );
114
- },
137
+ );
138
+
139
+ return Column(
140
+ crossAxisAlignment: CrossAxisAlignment.start,
141
+ children: [
142
+ Padding(
143
+ padding: const EdgeInsets.only(top: 20, left: 24, right: 24),
144
+ child: Text(
145
+ title,
146
+ style: TextStyle(
147
+ color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
148
+ fontSize: 16,
149
+ fontFamily: 'Lato',
150
+ fontWeight: FontWeight.bold,
151
+ decoration: TextDecoration.none,
152
+ ),
153
+ ),
154
+ ),
155
+ Container(
156
+ height: isSectionScrollable ? itemHeight - totalHeaderHeight : null,
157
+ child: isSectionScrollable
158
+ ? Scrollbar(
159
+ controller: _scrollController,
160
+ thumbVisibility: true,
161
+ child: sectionListView,
162
+ )
163
+ : sectionListView,
164
+ ),
165
+ ],
166
+ );
167
+ },
168
+ ),
169
+ ],
170
),
116
- ]),
171
+ ),
172
),
173
),
119
- )
120
- ],
174
+ ],
175
+ ),
176
);
177
}
178
}
lib/src/widgets/alert_close_button.dart
+13
-17
@@ -7,6 +7,7 @@ class AlertCloseButton extends StatelessWidget {
7
this.image,
8
this.bottom,
9
this.onTap,
10
+ this.isPositioned = true,
11
super.key,
12
});
13
@@ -14,6 +15,7 @@ class AlertCloseButton extends StatelessWidget {
15
16
final Image? image;
17
final double? bottom;
18
+ final bool isPositioned;
19
20
final closeButton = Image.asset(
21
'assets/images/close.png',
@@ -22,24 +24,18 @@ class AlertCloseButton extends StatelessWidget {
24
25
@override
26
Widget build(BuildContext context) {
25
- return Positioned(
26
- bottom: bottom ?? 60,
27
- child: GestureDetector(
27
+ final button = GestureDetector(
28
onTap: onTap ?? () => Navigator.of(context).pop(),
29
child: Semantics(
30
- label: S.of(context).close,
31
- button: true,
32
- enabled: true,
33
- child: Container(
34
- height: 42,
35
- width: 42,
36
- decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle),
37
- child: Center(
38
- child: image ?? closeButton,
39
- ),
40
- ),
41
- ),
42
- ),
43
- );
30
+ label: S.of(context).close,
31
+ button: true,
32
+ enabled: true,
33
+ child: Container(
34
+ height: 42,
35
+ width: 42,
36
+ decoration: BoxDecoration(color: Colors.white, shape: BoxShape.circle),
37
+ child: Center(child: image ?? closeButton))));
38
+
39
+ return isPositioned ? Positioned(bottom: bottom ?? 60, child: button) : button;
40
}
41
}