From e28e2fbdde43dd6a00910c51c485fb7c8cc52ecd Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Wed, 10 May 2023 09:19:38 -0300 Subject: [PATCH] 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 --- .../dashboard/widgets/filter_widget.dart | 168 +++++------ lib/src/widgets/check_box_picker.dart | 112 ++++--- lib/src/widgets/picker.dart | 274 ++++++++---------- lib/src/widgets/picker_wrapper_widget.dart | 61 ++++ 4 files changed, 310 insertions(+), 305 deletions(-) create mode 100644 lib/src/widgets/picker_wrapper_widget.dart diff --git a/lib/src/screens/dashboard/widgets/filter_widget.dart b/lib/src/screens/dashboard/widgets/filter_widget.dart index 9b8c87ea3..456ed95f7 100644 --- a/lib/src/screens/dashboard/widgets/filter_widget.dart +++ b/lib/src/screens/dashboard/widgets/filter_widget.dart @@ -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/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:flutter/cupertino.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: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; @@ -20,89 +16,93 @@ class FilterWidget extends StatelessWidget { @override Widget build(BuildContext context) { const sectionDivider = const SectionDivider(); - return AlertBackground( - child: Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - 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).textTheme!.bodyText1!.decorationColor!, - 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).primaryTextTheme.overline!.color!, - fontSize: 16, - fontFamily: 'Lato', - decoration: TextDecoration.none, - ), + 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).textTheme!.bodyText1!.decorationColor!, + 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) + .primaryTextTheme + .overline! + .color!, + fontSize: 16, + fontFamily: 'Lato', + decoration: TextDecoration.none, ), ), - sectionDivider, - ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: dashboardViewModel.filterItems.length, - separatorBuilder: (context, _) => sectionDivider, - itemBuilder: (_, index1) { - final title = dashboardViewModel.filterItems.keys.elementAt(index1); - final section = dashboardViewModel.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).primaryTextTheme!.headline6!.color!, - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.bold, - decoration: TextDecoration.none), - ), + ), + sectionDivider, + ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: dashboardViewModel.filterItems.length, + separatorBuilder: (context, _) => sectionDivider, + itemBuilder: (_, index1) { + final title = dashboardViewModel.filterItems.keys + .elementAt(index1); + final section = dashboardViewModel.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) + .primaryTextTheme! + .headline6! + .color!, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none), ), - ListView.builder( - padding: EdgeInsets.symmetric(vertical: 8.0), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: section.length, - itemBuilder: (_, index2) { - final item = section[index2]; - 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); - }, - ) - ], - ); - }, - ), - ]), - ), - ), - ), - ], + ), + ListView.builder( + padding: EdgeInsets.symmetric(vertical: 8.0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: section.length, + itemBuilder: (_, index2) { + final item = section[index2]; + 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); + }, + ) + ], + ); + }, + ), + ]), + ), ), - AlertCloseButton() - ], - ), + ) + ], ); } } diff --git a/lib/src/widgets/check_box_picker.dart b/lib/src/widgets/check_box_picker.dart index e874f587a..47b0be041 100644 --- a/lib/src/widgets/check_box_picker.dart +++ b/lib/src/widgets/check_box_picker.dart @@ -1,8 +1,7 @@ import 'package:cake_wallet/palette.dart'; 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'; +import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; class CheckBoxPicker extends StatefulWidget { CheckBoxPicker({ @@ -32,73 +31,57 @@ class CheckBoxPickerState extends State { @override Widget build(BuildContext context) { - return AlertBackground( - child: Column( - children: [ - Expanded( - child: Stack( - alignment: Alignment.center, - children: [ - Column( + return PickerWrapperWidget( + children: [ + if (widget.title.isNotEmpty) + Container( + padding: EdgeInsets.symmetric(horizontal: 24), + 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.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: [ - if (widget.title.isNotEmpty) - Container( - padding: EdgeInsets.symmetric(horizontal: 24), - 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.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: [ - items.length > 3 - ? Scrollbar( - controller: controller, - child: itemsList(), - ) - : itemsList(), - ], - ), - ), - ], - ), - ), - ), + children: [ + Flexible( + child: Stack( + alignment: Alignment.center, + children: [ + items.length > 3 + ? Scrollbar( + controller: controller, + child: itemsList(), + ) + : itemsList(), + ], ), ), ], ), - SizedBox(height: ResponsiveLayoutUtil.kPopupSpaceHeight), - AlertCloseButton(), - ], + ), ), ), - ], - ), + ), + ], ); } @@ -111,7 +94,10 @@ class CheckBoxPickerState extends State { shrinkWrap: true, separatorBuilder: (context, index) => widget.isSeparated ? Divider( - color: Theme.of(context).accentTextTheme.headline6!.backgroundColor!, + color: Theme.of(context) + .accentTextTheme + .headline6! + .backgroundColor!, height: 1, ) : const SizedBox(), diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index 34ff10316..e160a083b 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -2,9 +2,8 @@ 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'; import 'package:cw_core/currency.dart'; +import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; class Picker extends StatefulWidget { Picker({ @@ -114,171 +113,130 @@ class _PickerState extends State> { 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; - - 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( - child: Column( - children: [ - Expanded( - flex: 1, - child: Stack( - alignment: Alignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (widget.title?.isNotEmpty ?? false) - Container( - padding: EdgeInsets.symmetric(horizontal: padding), - 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: [ - 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), - ], + return PickerWrapperWidget( + hasTitle: widget.title?.isNotEmpty ?? false, + children: [ + if (widget.title?.isNotEmpty ?? false) + Container( + padding: EdgeInsets.symmetric(horizontal: padding), + child: Text( + widget.title!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.white, + ), ), ), - // gives the extra spacing using MediaQuery.viewInsets.bottom - // to simulate a keyboard area - SizedBox( - height: bottom, - ) - ], - ), + 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: [ + 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(), + ], + ), + ), + ], + ), + ), + ), + ), + ) + ], ); } diff --git a/lib/src/widgets/picker_wrapper_widget.dart b/lib/src/widgets/picker_wrapper_widget.dart new file mode 100644 index 000000000..244199936 --- /dev/null +++ b/lib/src/widgets/picker_wrapper_widget.dart @@ -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 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: [ + 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, + ) + ], + ), + ); + } +}