CW-376-picker-ui-issue (#919)
* feat: use common modal widget for repeated picker logic and display * refactor: rename widget * refactor: clear wrapper logic from picker widget and move title to hasTitle * Minor code readability enhancements [skip ci] --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Rafael Saes committed
May 10, 2023 at 09:19 UTC
e28e2fbdde43dd6a00910c51c485fb7c8cc52ecd
4 files changed
+300
-295
lib/src/screens/dashboard/widgets/filter_widget.dart
+84
-84
@@ -1,13 +1,9 @@
1
-import 'dart:ui';
2
-import 'package:cake_wallet/palette.dart';
1
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.dart';
2
import 'package:cake_wallet/src/widgets/section_divider.dart';
3
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
4
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
7
-import 'package:flutter/cupertino.dart';
5
import 'package:flutter/material.dart';
9
-import 'package:cake_wallet/src/widgets/alert_background.dart';
10
-import 'package:cake_wallet/src/widgets/alert_close_button.dart';
6
+import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
7
import 'package:cake_wallet/generated/i18n.dart';
8
import 'package:flutter_mobx/flutter_mobx.dart';
9
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
@@ -20,89 +16,93 @@ class FilterWidget extends StatelessWidget {
16
@override
17
Widget build(BuildContext context) {
18
const sectionDivider = const SectionDivider();
23
- return AlertBackground(
24
- child: Stack(
25
- alignment: Alignment.center,
26
- children: <Widget>[
27
- Column(
28
- mainAxisSize: MainAxisSize.min,
29
- children: <Widget>[
30
- Padding(
31
- padding: EdgeInsets.only(left: 24, right: 24, top: 24),
32
- child: ClipRRect(
33
- borderRadius: BorderRadius.all(Radius.circular(24)),
34
- child: Container(
35
- color: Theme.of(context).textTheme!.bodyText1!.decorationColor!,
36
- child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
37
- Padding(
38
- padding: EdgeInsets.all(24.0),
39
- child: Text(
40
- S.of(context).filter_by,
41
- style: TextStyle(
42
- color: Theme.of(context).primaryTextTheme.overline!.color!,
43
- fontSize: 16,
44
- fontFamily: 'Lato',
45
- decoration: TextDecoration.none,
46
- ),
19
+ return PickerWrapperWidget(
20
+ children: [
21
+ Padding(
22
+ padding: EdgeInsets.only(left: 24, right: 24, top: 24),
23
+ child: ClipRRect(
24
+ borderRadius: BorderRadius.all(Radius.circular(24)),
25
+ child: Container(
26
+ color: Theme.of(context).textTheme!.bodyText1!.decorationColor!,
27
+ child: Column(
28
+ crossAxisAlignment: CrossAxisAlignment.start,
29
+ children: [
30
+ Padding(
31
+ padding: EdgeInsets.all(24.0),
32
+ child: Text(
33
+ S.of(context).filter_by,
34
+ style: TextStyle(
35
+ color: Theme.of(context)
36
+ .primaryTextTheme
37
+ .overline!
38
+ .color!,
39
+ fontSize: 16,
40
+ fontFamily: 'Lato',
41
+ decoration: TextDecoration.none,
42
),
43
),
49
- sectionDivider,
50
- ListView.separated(
51
- padding: EdgeInsets.zero,
52
- shrinkWrap: true,
53
- physics: const NeverScrollableScrollPhysics(),
54
- itemCount: dashboardViewModel.filterItems.length,
55
- separatorBuilder: (context, _) => sectionDivider,
56
- itemBuilder: (_, index1) {
57
- final title = dashboardViewModel.filterItems.keys.elementAt(index1);
58
- final section = dashboardViewModel.filterItems.values.elementAt(index1);
59
- return Column(
60
- crossAxisAlignment: CrossAxisAlignment.start,
61
- children: <Widget>[
62
- Padding(
63
- padding: EdgeInsets.only(top: 20, left: 24, right: 24),
64
- child: Text(
65
- title,
66
- style: TextStyle(
67
- color: Theme.of(context).primaryTextTheme!.headline6!.color!,
68
- fontSize: 16,
69
- fontFamily: 'Lato',
70
- fontWeight: FontWeight.bold,
71
- decoration: TextDecoration.none),
72
- ),
44
+ ),
45
+ sectionDivider,
46
+ ListView.separated(
47
+ padding: EdgeInsets.zero,
48
+ shrinkWrap: true,
49
+ physics: const NeverScrollableScrollPhysics(),
50
+ itemCount: dashboardViewModel.filterItems.length,
51
+ separatorBuilder: (context, _) => sectionDivider,
52
+ itemBuilder: (_, index1) {
53
+ final title = dashboardViewModel.filterItems.keys
54
+ .elementAt(index1);
55
+ final section = dashboardViewModel.filterItems.values
56
+ .elementAt(index1);
57
+ return Column(
58
+ crossAxisAlignment: CrossAxisAlignment.start,
59
+ children: <Widget>[
60
+ Padding(
61
+ padding:
62
+ EdgeInsets.only(top: 20, left: 24, right: 24),
63
+ child: Text(
64
+ title,
65
+ style: TextStyle(
66
+ color: Theme.of(context)
67
+ .primaryTextTheme!
68
+ .headline6!
69
+ .color!,
70
+ fontSize: 16,
71
+ fontFamily: 'Lato',
72
+ fontWeight: FontWeight.bold,
73
+ decoration: TextDecoration.none),
74
),
74
- ListView.builder(
75
- padding: EdgeInsets.symmetric(vertical: 8.0),
76
- shrinkWrap: true,
77
- physics: const NeverScrollableScrollPhysics(),
78
- itemCount: section.length,
79
- itemBuilder: (_, index2) {
80
- final item = section[index2];
81
- final content = Observer(
82
- builder: (_) => StandardCheckbox(
83
- value: item.value(),
84
- caption: item.caption,
85
- gradientBackground: true,
86
- borderColor: Theme.of(context).dividerColor,
87
- iconColor: Colors.white,
88
- onChanged: (value) => item.onChanged(),
89
- ));
90
- return FilterTile(child: content);
91
- },
92
- )
93
- ],
94
- );
95
- },
96
- ),
97
- ]),
98
- ),
99
- ),
100
- ),
101
- ],
75
+ ),
76
+ ListView.builder(
77
+ padding: EdgeInsets.symmetric(vertical: 8.0),
78
+ shrinkWrap: true,
79
+ physics: const NeverScrollableScrollPhysics(),
80
+ itemCount: section.length,
81
+ itemBuilder: (_, index2) {
82
+ final item = section[index2];
83
+ final content = Observer(
84
+ builder: (_) => StandardCheckbox(
85
+ value: item.value(),
86
+ caption: item.caption,
87
+ gradientBackground: true,
88
+ borderColor:
89
+ Theme.of(context).dividerColor,
90
+ iconColor: Colors.white,
91
+ onChanged: (value) =>
92
+ item.onChanged(),
93
+ ));
94
+ return FilterTile(child: content);
95
+ },
96
+ )
97
+ ],
98
+ );
99
+ },
100
+ ),
101
+ ]),
102
+ ),
103
),
103
- AlertCloseButton()
104
- ],
105
- ),
104
+ )
105
+ ],
106
);
107
}
108
}
lib/src/widgets/check_box_picker.dart
+49
-63
@@ -1,8 +1,7 @@
1
import 'package:cake_wallet/palette.dart';
2
import 'package:cake_wallet/utils/responsive_layout_util.dart';
3
import 'package:flutter/material.dart';
4
-import 'package:cake_wallet/src/widgets/alert_background.dart';
5
-import 'package:cake_wallet/src/widgets/alert_close_button.dart';
4
+import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
5
6
class CheckBoxPicker extends StatefulWidget {
7
CheckBoxPicker({
@@ -32,73 +31,57 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
31
32
@override
33
Widget build(BuildContext context) {
35
- return AlertBackground(
36
- child: Column(
37
- children: [
38
- Expanded(
39
- child: Stack(
40
- alignment: Alignment.center,
41
- children: [
42
- Column(
34
+ return PickerWrapperWidget(
35
+ children: [
36
+ if (widget.title.isNotEmpty)
37
+ Container(
38
+ padding: EdgeInsets.symmetric(horizontal: 24),
39
+ child: Text(
40
+ widget.title,
41
+ textAlign: TextAlign.center,
42
+ style: TextStyle(
43
+ fontSize: 18,
44
+ fontFamily: 'Lato',
45
+ fontWeight: FontWeight.bold,
46
+ decoration: TextDecoration.none,
47
+ color: Colors.white,
48
+ ),
49
+ ),
50
+ ),
51
+ Padding(
52
+ padding: EdgeInsets.only(left: 24, right: 24, top: 24),
53
+ child: ClipRRect(
54
+ borderRadius: BorderRadius.all(Radius.circular(30)),
55
+ child: Container(
56
+ color: Theme.of(context).accentTextTheme.headline6!.color!,
57
+ child: ConstrainedBox(
58
+ constraints: BoxConstraints(
59
+ maxHeight: MediaQuery.of(context).size.height * 0.65,
60
+ maxWidth: ResponsiveLayoutUtil.kPopupWidth,
61
+ ),
62
+ child: Column(
63
mainAxisSize: MainAxisSize.min,
44
- children: <Widget>[
45
- if (widget.title.isNotEmpty)
46
- Container(
47
- padding: EdgeInsets.symmetric(horizontal: 24),
48
- child: Text(
49
- widget.title,
50
- textAlign: TextAlign.center,
51
- style: TextStyle(
52
- fontSize: 18,
53
- fontFamily: 'Lato',
54
- fontWeight: FontWeight.bold,
55
- decoration: TextDecoration.none,
56
- color: Colors.white,
57
- ),
58
- ),
59
- ),
60
- Padding(
61
- padding: EdgeInsets.only(left: 24, right: 24, top: 24),
62
- child: ClipRRect(
63
- borderRadius: BorderRadius.all(Radius.circular(30)),
64
- child: Container(
65
- color: Theme.of(context).accentTextTheme.headline6!.color!,
66
- child: ConstrainedBox(
67
- constraints: BoxConstraints(
68
- maxHeight: MediaQuery.of(context).size.height * 0.65,
69
- maxWidth: ResponsiveLayoutUtil.kPopupWidth,
70
- ),
71
- child: Column(
72
- mainAxisSize: MainAxisSize.min,
73
- children: [
74
- Flexible(
75
- child: Stack(
76
- alignment: Alignment.center,
77
- children: <Widget>[
78
- items.length > 3
79
- ? Scrollbar(
80
- controller: controller,
81
- child: itemsList(),
82
- )
83
- : itemsList(),
84
- ],
85
- ),
86
- ),
87
- ],
88
- ),
89
- ),
90
- ),
64
+ children: [
65
+ Flexible(
66
+ child: Stack(
67
+ alignment: Alignment.center,
68
+ children: <Widget>[
69
+ items.length > 3
70
+ ? Scrollbar(
71
+ controller: controller,
72
+ child: itemsList(),
73
+ )
74
+ : itemsList(),
75
+ ],
76
),
77
),
78
],
79
),
95
- SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
96
- AlertCloseButton(),
97
- ],
80
+ ),
81
),
82
),
100
- ],
101
- ),
83
+ ),
84
+ ],
85
);
86
}
87
@@ -111,7 +94,10 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
94
shrinkWrap: true,
95
separatorBuilder: (context, index) => widget.isSeparated
96
? Divider(
114
- color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!,
97
+ color: Theme.of(context)
98
+ .accentTextTheme
99
+ .headline6!
100
+ .backgroundColor!,
101
height: 1,
102
)
103
: const SizedBox(),
lib/src/widgets/picker.dart
+106
-148
@@ -2,9 +2,8 @@
2
3
import 'package:cake_wallet/utils/responsive_layout_util.dart';
4
import 'package:flutter/material.dart';
5
-import 'package:cake_wallet/src/widgets/alert_background.dart';
6
-import 'package:cake_wallet/src/widgets/alert_close_button.dart';
5
import 'package:cw_core/currency.dart';
6
+import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
7
8
class Picker<Item> extends StatefulWidget {
9
Picker({
@@ -114,171 +113,130 @@ class _PickerState<Item> extends State<Picker<Item>> {
113
final mq = MediaQuery.of(context);
114
final bottom = mq.viewInsets.bottom;
115
final height = mq.size.height - bottom;
117
- final screenCenter = height / 2;
116
119
- double closeButtonBottom = 60;
117
double containerHeight = height * 0.65;
118
if (bottom > 0) {
119
// increase a bit or it gets too squished in the top
120
containerHeight = height * 0.75;
124
-
125
- final containerCenter = containerHeight / 2;
126
- final containerBottom = screenCenter - containerCenter;
127
-
128
- final hasTitle = widget.title == null || widget.title!.isEmpty;
129
-
130
- // position the close button right below the search container
131
- closeButtonBottom = closeButtonBottom -
132
- containerBottom +
133
- (hasTitle ? padding : padding / 1.5);
121
}
122
136
- return AlertBackground(
137
- child: Column(
138
- children: [
139
- Expanded(
140
- flex: 1,
141
- child: Stack(
142
- alignment: Alignment.center,
143
- children: <Widget>[
144
- Column(
123
+ return PickerWrapperWidget(
124
+ hasTitle: widget.title?.isNotEmpty ?? false,
125
+ children: [
126
+ if (widget.title?.isNotEmpty ?? false)
127
+ Container(
128
+ padding: EdgeInsets.symmetric(horizontal: padding),
129
+ child: Text(
130
+ widget.title!,
131
+ textAlign: TextAlign.center,
132
+ style: TextStyle(
133
+ fontSize: 18,
134
+ fontFamily: 'Lato',
135
+ fontWeight: FontWeight.bold,
136
+ decoration: TextDecoration.none,
137
+ color: Colors.white,
138
+ ),
139
+ ),
140
+ ),
141
+ Padding(
142
+ padding: EdgeInsets.symmetric(horizontal: padding),
143
+ child: ClipRRect(
144
+ borderRadius: BorderRadius.all(Radius.circular(30)),
145
+ child: Container(
146
+ color: Theme.of(context).accentTextTheme.headline6!.color!,
147
+ child: ConstrainedBox(
148
+ constraints: BoxConstraints(
149
+ maxHeight: containerHeight,
150
+ maxWidth: ResponsiveLayoutUtil.kPopupWidth,
151
+ ),
152
+ child: Column(
153
mainAxisSize: MainAxisSize.min,
146
- mainAxisAlignment: MainAxisAlignment.center,
147
- children: <Widget>[
148
- if (widget.title?.isNotEmpty ?? false)
149
- Container(
150
- padding: EdgeInsets.symmetric(horizontal: padding),
151
- child: Text(
152
- widget.title!,
153
- textAlign: TextAlign.center,
154
+ children: [
155
+ if (widget.hintText != null)
156
+ Padding(
157
+ padding: const EdgeInsets.all(16),
158
+ child: TextFormField(
159
+ controller: searchController,
160
style: TextStyle(
155
- fontSize: 18,
156
- fontFamily: 'Lato',
157
- fontWeight: FontWeight.bold,
158
- decoration: TextDecoration.none,
159
- color: Colors.white,
161
+ color: Theme.of(context)
162
+ .primaryTextTheme
163
+ .headline6!
164
+ .color!),
165
+ decoration: InputDecoration(
166
+ hintText: widget.hintText,
167
+ prefixIcon:
168
+ Image.asset("assets/images/search_icon.png"),
169
+ filled: true,
170
+ fillColor: Theme.of(context)
171
+ .accentTextTheme
172
+ .headline3!
173
+ .color!,
174
+ alignLabelWithHint: false,
175
+ contentPadding: const EdgeInsets.symmetric(
176
+ vertical: 4, horizontal: 16),
177
+ enabledBorder: OutlineInputBorder(
178
+ borderRadius: BorderRadius.circular(14),
179
+ borderSide: const BorderSide(
180
+ color: Colors.transparent,
181
+ )),
182
+ focusedBorder: OutlineInputBorder(
183
+ borderRadius: BorderRadius.circular(14),
184
+ borderSide: const BorderSide(
185
+ color: Colors.transparent,
186
+ )),
187
),
188
),
189
),
163
- Padding(
164
- padding: EdgeInsets.symmetric(horizontal: padding),
165
- child: ClipRRect(
166
- borderRadius: BorderRadius.all(Radius.circular(30)),
167
- child: Container(
168
- color: Theme.of(context)
169
- .accentTextTheme
170
- .headline6!
171
- .color!,
172
- child: ConstrainedBox(
173
- constraints: BoxConstraints(
174
- maxHeight: containerHeight,
175
- maxWidth: ResponsiveLayoutUtil.kPopupWidth,
176
- ),
177
- child: Column(
178
- mainAxisSize: MainAxisSize.min,
179
- children: [
180
- if (widget.hintText != null)
181
- Padding(
182
- padding: const EdgeInsets.all(16),
183
- child: TextFormField(
184
- controller: searchController,
185
- style: TextStyle(
186
- color: Theme.of(context)
187
- .primaryTextTheme
188
- .headline6!
189
- .color!),
190
- decoration: InputDecoration(
191
- hintText: widget.hintText,
192
- prefixIcon: Image.asset(
193
- "assets/images/search_icon.png"),
194
- filled: true,
195
- fillColor: Theme.of(context)
196
- .accentTextTheme
197
- .headline3!
198
- .color!,
199
- alignLabelWithHint: false,
200
- contentPadding:
201
- const EdgeInsets.symmetric(
202
- vertical: 4, horizontal: 16),
203
- enabledBorder: OutlineInputBorder(
204
- borderRadius:
205
- BorderRadius.circular(14),
206
- borderSide: const BorderSide(
207
- color: Colors.transparent,
208
- )),
209
- focusedBorder: OutlineInputBorder(
210
- borderRadius:
211
- BorderRadius.circular(14),
212
- borderSide: const BorderSide(
213
- color: Colors.transparent,
214
- )),
215
- ),
190
+ Divider(
191
+ color: Theme.of(context)
192
+ .accentTextTheme
193
+ .headline6!
194
+ .backgroundColor!,
195
+ height: 1,
196
+ ),
197
+ if (widget.selectedAtIndex != -1)
198
+ buildSelectedItem(widget.selectedAtIndex),
199
+ Flexible(
200
+ child: Stack(
201
+ alignment: Alignment.center,
202
+ children: <Widget>[
203
+ filteredItems.length > 3
204
+ ? Scrollbar(
205
+ controller: controller,
206
+ child: itemsList(),
207
+ )
208
+ : itemsList(),
209
+ (widget.description?.isNotEmpty ?? false)
210
+ ? Positioned(
211
+ bottom: padding,
212
+ left: padding,
213
+ right: padding,
214
+ child: Text(
215
+ widget.description!,
216
+ textAlign: TextAlign.center,
217
+ style: TextStyle(
218
+ fontSize: 12,
219
+ fontWeight: FontWeight.w500,
220
+ fontFamily: 'Lato',
221
+ decoration: TextDecoration.none,
222
+ color: Theme.of(context)
223
+ .primaryTextTheme
224
+ .headline6!
225
+ .color!,
226
),
227
),
218
- Divider(
219
- color: Theme.of(context)
220
- .accentTextTheme
221
- .headline6!
222
- .backgroundColor!,
223
- height: 1,
224
- ),
225
- if (widget.selectedAtIndex != -1)
226
- buildSelectedItem(widget.selectedAtIndex),
227
- Flexible(
228
- child: Stack(
229
- alignment: Alignment.center,
230
- children: <Widget>[
231
- filteredItems.length > 3
232
- ? Scrollbar(
233
- controller: controller,
234
- child: itemsList(),
235
- )
236
- : itemsList(),
237
- (widget.description?.isNotEmpty ?? false)
238
- ? Positioned(
239
- bottom: padding,
240
- left: padding,
241
- right: padding,
242
- child: Text(
243
- widget.description!,
244
- textAlign: TextAlign.center,
245
- style: TextStyle(
246
- fontSize: 12,
247
- fontWeight: FontWeight.w500,
248
- fontFamily: 'Lato',
249
- decoration:
250
- TextDecoration.none,
251
- color: Theme.of(context)
252
- .primaryTextTheme
253
- .headline6!
254
- .color!,
255
- ),
256
- ),
257
- )
258
- : Offstage(),
259
- ],
260
- ),
261
- ),
262
- ],
263
- ),
264
- ),
265
- ),
228
+ )
229
+ : Offstage(),
230
+ ],
231
),
267
- )
232
+ ),
233
],
234
),
270
- SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
271
- AlertCloseButton(bottom: closeButtonBottom),
272
- ],
235
+ ),
236
),
237
),
275
- // gives the extra spacing using MediaQuery.viewInsets.bottom
276
- // to simulate a keyboard area
277
- SizedBox(
278
- height: bottom,
279
- )
280
- ],
281
- ),
238
+ )
239
+ ],
240
);
241
}
242
lib/src/widgets/picker_wrapper_widget.dart
new
+61
@@ -0,0 +1,61 @@
1
+import 'package:cake_wallet/utils/responsive_layout_util.dart';
2
+import 'package:flutter/material.dart';
3
+import 'package:cake_wallet/src/widgets/alert_background.dart';
4
+import 'package:cake_wallet/src/widgets/alert_close_button.dart';
5
+
6
+class PickerWrapperWidget extends StatelessWidget {
7
+ PickerWrapperWidget({required this.children, this.hasTitle = false});
8
+
9
+ final List<Widget> children;
10
+ final bool hasTitle;
11
+
12
+ @override
13
+ Widget build(BuildContext context) {
14
+ final double padding = 24;
15
+
16
+ final mq = MediaQuery.of(context);
17
+ final bottom = mq.viewInsets.bottom;
18
+ final height = mq.size.height - bottom;
19
+ final screenCenter = height / 2;
20
+
21
+ double closeButtonBottom = 60;
22
+ double containerHeight = height * 0.65;
23
+ if (bottom > 0) {
24
+ // increase a bit or it gets too squished in the top
25
+ containerHeight = height * 0.75;
26
+
27
+ final containerCenter = containerHeight / 2;
28
+ final containerBottom = screenCenter - containerCenter;
29
+
30
+ // position the close button right below the search container
31
+ closeButtonBottom = closeButtonBottom -
32
+ containerBottom + (!hasTitle ? padding : padding / 1.5);
33
+ }
34
+
35
+ return AlertBackground(
36
+ child: Column(
37
+ children: [
38
+ Expanded(
39
+ flex: 1,
40
+ child: Stack(
41
+ alignment: Alignment.center,
42
+ children: <Widget>[
43
+ Column(
44
+ mainAxisSize: MainAxisSize.min,
45
+ children: children,
46
+ ),
47
+ SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
48
+ AlertCloseButton(bottom: closeButtonBottom),
49
+ ],
50
+ ),
51
+ ),
52
+ // gives the extra spacing using MediaQuery.viewInsets.bottom
53
+ // to simulate a keyboard area
54
+ SizedBox(
55
+ height: bottom,
56
+ )
57
+ ],
58
+ ),
59
+ );
60
+ }
61
+}