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>
This commit is contained in:
Rafael Saes 2023-05-10 09:19:38 -03:00 committed by GitHub
parent 759e61f67e
commit e28e2fbdde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 310 additions and 305 deletions

View file

@ -1,13 +1,9 @@
import 'dart:ui';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/filter_tile.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/section_divider.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
//import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker; //import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
@ -20,89 +16,93 @@ class FilterWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const sectionDivider = const SectionDivider(); const sectionDivider = const SectionDivider();
return AlertBackground( return PickerWrapperWidget(
child: Stack( children: [
alignment: Alignment.center, Padding(
children: <Widget>[ padding: EdgeInsets.only(left: 24, right: 24, top: 24),
Column( child: ClipRRect(
mainAxisSize: MainAxisSize.min, borderRadius: BorderRadius.all(Radius.circular(24)),
children: <Widget>[ child: Container(
Padding( color: Theme.of(context).textTheme!.bodyText1!.decorationColor!,
padding: EdgeInsets.only(left: 24, right: 24, top: 24), child: Column(
child: ClipRRect( crossAxisAlignment: CrossAxisAlignment.start,
borderRadius: BorderRadius.all(Radius.circular(24)), children: [
child: Container( Padding(
color: Theme.of(context).textTheme!.bodyText1!.decorationColor!, padding: EdgeInsets.all(24.0),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Text(
Padding( S.of(context).filter_by,
padding: EdgeInsets.all(24.0), style: TextStyle(
child: Text( color: Theme.of(context)
S.of(context).filter_by, .primaryTextTheme
style: TextStyle( .overline!
color: Theme.of(context).primaryTextTheme.overline!.color!, .color!,
fontSize: 16, fontSize: 16,
fontFamily: 'Lato', fontFamily: 'Lato',
decoration: TextDecoration.none, decoration: TextDecoration.none,
),
), ),
), ),
sectionDivider, ),
ListView.separated( sectionDivider,
padding: EdgeInsets.zero, ListView.separated(
shrinkWrap: true, padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(), shrinkWrap: true,
itemCount: dashboardViewModel.filterItems.length, physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, _) => sectionDivider, itemCount: dashboardViewModel.filterItems.length,
itemBuilder: (_, index1) { separatorBuilder: (context, _) => sectionDivider,
final title = dashboardViewModel.filterItems.keys.elementAt(index1); itemBuilder: (_, index1) {
final section = dashboardViewModel.filterItems.values.elementAt(index1); final title = dashboardViewModel.filterItems.keys
return Column( .elementAt(index1);
crossAxisAlignment: CrossAxisAlignment.start, final section = dashboardViewModel.filterItems.values
children: <Widget>[ .elementAt(index1);
Padding( return Column(
padding: EdgeInsets.only(top: 20, left: 24, right: 24), crossAxisAlignment: CrossAxisAlignment.start,
child: Text( children: <Widget>[
title, Padding(
style: TextStyle( padding:
color: Theme.of(context).primaryTextTheme!.headline6!.color!, EdgeInsets.only(top: 20, left: 24, right: 24),
fontSize: 16, child: Text(
fontFamily: 'Lato', title,
fontWeight: FontWeight.bold, style: TextStyle(
decoration: TextDecoration.none), color: Theme.of(context)
), .primaryTextTheme!
.headline6!
.color!,
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.bold,
decoration: TextDecoration.none),
), ),
ListView.builder( ),
padding: EdgeInsets.symmetric(vertical: 8.0), ListView.builder(
shrinkWrap: true, padding: EdgeInsets.symmetric(vertical: 8.0),
physics: const NeverScrollableScrollPhysics(), shrinkWrap: true,
itemCount: section.length, physics: const NeverScrollableScrollPhysics(),
itemBuilder: (_, index2) { itemCount: section.length,
final item = section[index2]; itemBuilder: (_, index2) {
final content = Observer( final item = section[index2];
builder: (_) => StandardCheckbox( final content = Observer(
value: item.value(), builder: (_) => StandardCheckbox(
caption: item.caption, value: item.value(),
gradientBackground: true, caption: item.caption,
borderColor: Theme.of(context).dividerColor, gradientBackground: true,
iconColor: Colors.white, borderColor:
onChanged: (value) => item.onChanged(), Theme.of(context).dividerColor,
)); iconColor: Colors.white,
return FilterTile(child: content); onChanged: (value) =>
}, item.onChanged(),
) ));
], return FilterTile(child: content);
); },
}, )
), ],
]), );
), },
), ),
), ]),
], ),
), ),
AlertCloseButton() )
], ],
),
); );
} }
} }

View file

@ -1,8 +1,7 @@
import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
class CheckBoxPicker extends StatefulWidget { class CheckBoxPicker extends StatefulWidget {
CheckBoxPicker({ CheckBoxPicker({
@ -32,73 +31,57 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertBackground( return PickerWrapperWidget(
child: Column( children: [
children: [ if (widget.title.isNotEmpty)
Expanded( Container(
child: Stack( padding: EdgeInsets.symmetric(horizontal: 24),
alignment: Alignment.center, child: Text(
children: [ widget.title,
Column( textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white,
),
),
),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, top: 24),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container(
color: Theme.of(context).accentTextTheme.headline6!.color!,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.65,
maxWidth: ResponsiveLayoutUtil.kPopupWidth,
),
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: [
if (widget.title.isNotEmpty) Flexible(
Container( child: Stack(
padding: EdgeInsets.symmetric(horizontal: 24), alignment: Alignment.center,
child: Text( children: <Widget>[
widget.title, items.length > 3
textAlign: TextAlign.center, ? Scrollbar(
style: TextStyle( controller: controller,
fontSize: 18, child: itemsList(),
fontFamily: 'Lato', )
fontWeight: FontWeight.bold, : itemsList(),
decoration: TextDecoration.none, ],
color: Colors.white,
),
),
),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, top: 24),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container(
color: Theme.of(context).accentTextTheme.headline6!.color!,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.65,
maxWidth: ResponsiveLayoutUtil.kPopupWidth,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
items.length > 3
? Scrollbar(
controller: controller,
child: itemsList(),
)
: itemsList(),
],
),
),
],
),
),
),
), ),
), ),
], ],
), ),
SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), ),
AlertCloseButton(),
],
), ),
), ),
], ),
), ],
); );
} }
@ -111,7 +94,10 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
shrinkWrap: true, shrinkWrap: true,
separatorBuilder: (context, index) => widget.isSeparated separatorBuilder: (context, index) => widget.isSeparated
? Divider( ? Divider(
color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, color: Theme.of(context)
.accentTextTheme
.headline6!
.backgroundColor!,
height: 1, height: 1,
) )
: const SizedBox(), : const SizedBox(),

View file

@ -2,9 +2,8 @@
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
import 'package:cw_core/currency.dart'; import 'package:cw_core/currency.dart';
import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart';
class Picker<Item> extends StatefulWidget { class Picker<Item> extends StatefulWidget {
Picker({ Picker({
@ -114,171 +113,130 @@ class _PickerState<Item> extends State<Picker<Item>> {
final mq = MediaQuery.of(context); final mq = MediaQuery.of(context);
final bottom = mq.viewInsets.bottom; final bottom = mq.viewInsets.bottom;
final height = mq.size.height - bottom; final height = mq.size.height - bottom;
final screenCenter = height / 2;
double closeButtonBottom = 60;
double containerHeight = height * 0.65; double containerHeight = height * 0.65;
if (bottom > 0) { if (bottom > 0) {
// increase a bit or it gets too squished in the top // increase a bit or it gets too squished in the top
containerHeight = height * 0.75; containerHeight = height * 0.75;
final containerCenter = containerHeight / 2;
final containerBottom = screenCenter - containerCenter;
final hasTitle = widget.title == null || widget.title!.isEmpty;
// position the close button right below the search container
closeButtonBottom = closeButtonBottom -
containerBottom +
(hasTitle ? padding : padding / 1.5);
} }
return AlertBackground( return PickerWrapperWidget(
child: Column( hasTitle: widget.title?.isNotEmpty ?? false,
children: [ children: [
Expanded( if (widget.title?.isNotEmpty ?? false)
flex: 1, Container(
child: Stack( padding: EdgeInsets.symmetric(horizontal: padding),
alignment: Alignment.center, child: Text(
children: <Widget>[ widget.title!,
Column( textAlign: TextAlign.center,
mainAxisSize: MainAxisSize.min, style: TextStyle(
mainAxisAlignment: MainAxisAlignment.center, fontSize: 18,
children: <Widget>[ fontFamily: 'Lato',
if (widget.title?.isNotEmpty ?? false) fontWeight: FontWeight.bold,
Container( decoration: TextDecoration.none,
padding: EdgeInsets.symmetric(horizontal: padding), color: Colors.white,
child: Text( ),
widget.title!,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white,
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: padding),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container(
color: Theme.of(context)
.accentTextTheme
.headline6!
.color!,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: containerHeight,
maxWidth: ResponsiveLayoutUtil.kPopupWidth,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.hintText != null)
Padding(
padding: const EdgeInsets.all(16),
child: TextFormField(
controller: searchController,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline6!
.color!),
decoration: InputDecoration(
hintText: widget.hintText,
prefixIcon: Image.asset(
"assets/images/search_icon.png"),
filled: true,
fillColor: Theme.of(context)
.accentTextTheme
.headline3!
.color!,
alignLabelWithHint: false,
contentPadding:
const EdgeInsets.symmetric(
vertical: 4, horizontal: 16),
enabledBorder: OutlineInputBorder(
borderRadius:
BorderRadius.circular(14),
borderSide: const BorderSide(
color: Colors.transparent,
)),
focusedBorder: OutlineInputBorder(
borderRadius:
BorderRadius.circular(14),
borderSide: const BorderSide(
color: Colors.transparent,
)),
),
),
),
Divider(
color: Theme.of(context)
.accentTextTheme
.headline6!
.backgroundColor!,
height: 1,
),
if (widget.selectedAtIndex != -1)
buildSelectedItem(widget.selectedAtIndex),
Flexible(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
filteredItems.length > 3
? Scrollbar(
controller: controller,
child: itemsList(),
)
: itemsList(),
(widget.description?.isNotEmpty ?? false)
? Positioned(
bottom: padding,
left: padding,
right: padding,
child: Text(
widget.description!,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
decoration:
TextDecoration.none,
color: Theme.of(context)
.primaryTextTheme
.headline6!
.color!,
),
),
)
: Offstage(),
],
),
),
],
),
),
),
),
)
],
),
SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
AlertCloseButton(bottom: closeButtonBottom),
],
), ),
), ),
// gives the extra spacing using MediaQuery.viewInsets.bottom Padding(
// to simulate a keyboard area padding: EdgeInsets.symmetric(horizontal: padding),
SizedBox( child: ClipRRect(
height: bottom, borderRadius: BorderRadius.all(Radius.circular(30)),
) child: Container(
], color: Theme.of(context).accentTextTheme.headline6!.color!,
), child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: containerHeight,
maxWidth: ResponsiveLayoutUtil.kPopupWidth,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.hintText != null)
Padding(
padding: const EdgeInsets.all(16),
child: TextFormField(
controller: searchController,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headline6!
.color!),
decoration: InputDecoration(
hintText: widget.hintText,
prefixIcon:
Image.asset("assets/images/search_icon.png"),
filled: true,
fillColor: Theme.of(context)
.accentTextTheme
.headline3!
.color!,
alignLabelWithHint: false,
contentPadding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: Colors.transparent,
)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: Colors.transparent,
)),
),
),
),
Divider(
color: Theme.of(context)
.accentTextTheme
.headline6!
.backgroundColor!,
height: 1,
),
if (widget.selectedAtIndex != -1)
buildSelectedItem(widget.selectedAtIndex),
Flexible(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
filteredItems.length > 3
? Scrollbar(
controller: controller,
child: itemsList(),
)
: itemsList(),
(widget.description?.isNotEmpty ?? false)
? Positioned(
bottom: padding,
left: padding,
right: padding,
child: Text(
widget.description!,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
decoration: TextDecoration.none,
color: Theme.of(context)
.primaryTextTheme
.headline6!
.color!,
),
),
)
: Offstage(),
],
),
),
],
),
),
),
),
)
],
); );
} }

View file

@ -0,0 +1,61 @@
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
class PickerWrapperWidget extends StatelessWidget {
PickerWrapperWidget({required this.children, this.hasTitle = false});
final List<Widget> children;
final bool hasTitle;
@override
Widget build(BuildContext context) {
final double padding = 24;
final mq = MediaQuery.of(context);
final bottom = mq.viewInsets.bottom;
final height = mq.size.height - bottom;
final screenCenter = height / 2;
double closeButtonBottom = 60;
double containerHeight = height * 0.65;
if (bottom > 0) {
// increase a bit or it gets too squished in the top
containerHeight = height * 0.75;
final containerCenter = containerHeight / 2;
final containerBottom = screenCenter - containerCenter;
// position the close button right below the search container
closeButtonBottom = closeButtonBottom -
containerBottom + (!hasTitle ? padding : padding / 1.5);
}
return AlertBackground(
child: Column(
children: [
Expanded(
flex: 1,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: children,
),
SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight),
AlertCloseButton(bottom: closeButtonBottom),
],
),
),
// gives the extra spacing using MediaQuery.viewInsets.bottom
// to simulate a keyboard area
SizedBox(
height: bottom,
)
],
),
);
}
}