From be3404ffaf2ac9c4f75ea50644f8813ef971a511 Mon Sep 17 00:00:00 2001 From: Tamara Slosarek Date: Sat, 7 Sep 2024 19:17:38 +0200 Subject: [PATCH] feat(app): move filters to main page --- app/generate_screendocs/sequence_utils.dart | 4 - .../common/widgets/drug_search/builder.dart | 159 ++++++++++- .../widgets/drug_search/filter_button.dart | 64 ----- .../widgets/drug_search/filter_menu.dart | 247 ------------------ app/lib/common/widgets/module.dart | 2 - app/lib/common/widgets/page_scaffold.dart | 6 +- app/lib/l10n/app_en.arb | 6 +- app/lib/search/pages/search.dart | 6 - 8 files changed, 155 insertions(+), 339 deletions(-) delete mode 100644 app/lib/common/widgets/drug_search/filter_button.dart delete mode 100644 app/lib/common/widgets/drug_search/filter_menu.dart diff --git a/app/generate_screendocs/sequence_utils.dart b/app/generate_screendocs/sequence_utils.dart index 95c87dcb..d3e838f4 100644 --- a/app/generate_screendocs/sequence_utils.dart +++ b/app/generate_screendocs/sequence_utils.dart @@ -226,10 +226,6 @@ Future tapDrugSearchTooltip(WidgetTester tester) async { ).first); } -Future openDrugFilters(WidgetTester tester) async { - await tester.tap(find.byType(FilterButton).first); -} - Future closeDrugFilters(WidgetTester tester) async { await tester.tap(find.byKey(Key('close-filter-drawer-button')).first); } diff --git a/app/lib/common/widgets/drug_search/builder.dart b/app/lib/common/widgets/drug_search/builder.dart index e5fc1b11..8f7d7b5a 100644 --- a/app/lib/common/widgets/drug_search/builder.dart +++ b/app/lib/common/widgets/drug_search/builder.dart @@ -4,6 +4,43 @@ import 'package:flutter/cupertino.dart'; import '../../../../common/module.dart'; import '../../../drug/widgets/tooltip_icon.dart'; +class _FilterChip extends StatelessWidget { + const _FilterChip({ + required this.onPressed, + required this.label, + required this.color, + required this.borderColor, + }); + + final void Function()? onPressed; + final Widget label; + final Color color; + final Color borderColor; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onPressed, + child: DecoratedBox( + decoration: BoxDecoration( + color: color, + border: Border.all(color: borderColor), + borderRadius: BorderRadius.all( + Radius.circular(PharMeTheme.outerCardRadius) + ), + ), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: PharMeTheme.mediumSpace * 0.5, + vertical: PharMeTheme.mediumSpace * 0.4, + ), + child: label, + ), + ), + ); + } +} + class DrugSearch extends HookWidget { const DrugSearch({ super.key, @@ -28,6 +65,99 @@ class DrugSearch extends HookWidget { final DrugListState state; final ActiveDrugs activeDrugs; + int _getFilteredNumber({ + required FilterState itemFilter, + required List drugs, + }) { + return itemFilter + .filter(drugs, activeDrugs, searchForDrugClass: searchForDrugClass) + .length; + } + + bool _filterIsEnabled({ + required FilterState itemFilter, + required List drugs, + }) => _getFilteredNumber(itemFilter: itemFilter, drugs: drugs) > 0; + + Widget _getFilterText( + BuildContext context, + WarningLevel warningLevel, { + required FilterState itemFilter, + required List drugs, + bool enabled = true, + bool showText = true, + }) { + final numberTextColor = darkenColor(PharMeTheme.onSurfaceText, -0.2); + final disabledTextColor = darkenColor(numberTextColor, -0.2); + return Text.rich( + TextSpan(children: [ + WidgetSpan( + child: Icon( + enabled ? warningLevel.icon : warningLevel.outlinedIcon, + color: PharMeTheme.onSurfaceText, + size: PharMeTheme.textTheme.labelMedium!.fontSize, + )), + if (showText) ...[ + TextSpan(text: ' '), + TextSpan( + text: warningLevel.getLabel(context), + style: PharMeTheme.textTheme.labelSmall!.copyWith( + color: enabled + ? PharMeTheme.textTheme.labelSmall!.color + : disabledTextColor, + ), + ), + ], + TextSpan( + text: ' (${ + _getFilteredNumber(itemFilter: itemFilter, drugs: drugs) + })', + style: PharMeTheme.textTheme.labelSmall!.copyWith( + color: enabled ? numberTextColor : disabledTextColor, + ), + ), + ]), + ); + } + + List _buildWarningLevelFilters( + BuildContext context, + List drugs, + FilterState filter, + ) { + FilterState warningLevelFilter(WarningLevel warningLevel) { + final currentFilter = FilterState.from(filter); + currentFilter.showWarningLevel.forEach( + (currentWarningLevel, currentValue) => + currentFilter.showWarningLevel[currentWarningLevel] = + currentWarningLevel == warningLevel); + return currentFilter; + } + Widget buildWarningLevelItem(WarningLevel warningLevel) { + final value = filter.showWarningLevel[warningLevel]!; + final itemFilter = warningLevelFilter(warningLevel); + final enabled = _filterIsEnabled(itemFilter: itemFilter, drugs: drugs); + return _FilterChip( + onPressed: enabled ? () => cubit.search( + showWarningLevel: {warningLevel: !value}, + ) : null, + label: _getFilterText( + context, + warningLevel, + itemFilter: itemFilter, + drugs: drugs, + enabled: value && enabled, + showText: false, + ), + color: value && enabled ? warningLevel.color : Colors.transparent, + borderColor: value && enabled + ? warningLevel.color + : PharMeTheme.onSurfaceColor, + ); + } + return WarningLevel.values.map(buildWarningLevelItem).toList(); + } + @override Widget build(BuildContext context) { final searchController = useTextEditingController(); @@ -39,15 +169,30 @@ class DrugSearch extends HookWidget { right: PharMeTheme.smallSpace, bottom: PharMeTheme.smallSpace, ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ..._buildSearchBarItems(context, searchController), - if (showFilter) FilterButton( - state, - activeDrugs, - searchForDrugClass: searchForDrugClass, + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildSearchBarItems(context, searchController), ), + if (showFilter) state.whenOrNull( + loaded: (allDrugs, filter) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: PharMeTheme.smallSpace), + Wrap( + spacing: PharMeTheme.smallSpace, + runSpacing: PharMeTheme.smallSpace, + runAlignment: WrapAlignment.center, + alignment: WrapAlignment.start, + children: [ + ..._buildWarningLevelFilters(context, allDrugs, filter), + ], + ), + ], + ) + ) ?? SizedBox.shrink(), ], ), ), diff --git a/app/lib/common/widgets/drug_search/filter_button.dart b/app/lib/common/widgets/drug_search/filter_button.dart deleted file mode 100644 index 5d53063c..00000000 --- a/app/lib/common/widgets/drug_search/filter_button.dart +++ /dev/null @@ -1,64 +0,0 @@ -import '../../module.dart'; - -class FilterButton extends StatelessWidget { - const FilterButton( - this.state, - this.activeDrugs, - { - required this.searchForDrugClass, - } - ); - - final DrugListState state; - final ActiveDrugs activeDrugs; - final bool searchForDrugClass; - - @override - Widget build(BuildContext context) { - return IconButton( - icon: Stack( - children: [ - Icon(Icons.filter_list), - if (_showActiveIndicator()) _buildActiveIndicator(context), - ], - ), - color: PharMeTheme.iconColor, - onPressed: Scaffold.of(context).openDrawer, - ); - } - - bool _showActiveIndicator() { - final itemsAreFiltered = state.whenOrNull( - loaded: (allDrugs, filter) { - final totalNumberOfDrugs = allDrugs.length; - final currentNumberOfDrugs = filter.filter( - allDrugs, - activeDrugs, - searchForDrugClass: searchForDrugClass, - ).length; - return totalNumberOfDrugs != currentNumberOfDrugs; - }, - ); - return itemsAreFiltered ?? false; - } - - Widget _buildActiveIndicator(BuildContext context) { - const indicatorSize = PharMeTheme.smallToMediumSpace; - return Positioned( - right: 0, - bottom: 0, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: PharMeTheme.sinaiPurple, - border: Border.all( - color: PharMeTheme.surfaceColor, - width: indicatorSize / 8, - ), - ), - width: indicatorSize, - height: indicatorSize, - ), - ); - } -} diff --git a/app/lib/common/widgets/drug_search/filter_menu.dart b/app/lib/common/widgets/drug_search/filter_menu.dart deleted file mode 100644 index c5f21e95..00000000 --- a/app/lib/common/widgets/drug_search/filter_menu.dart +++ /dev/null @@ -1,247 +0,0 @@ -import '../../module.dart'; - -class FilterMenuItem { - FilterMenuItem({ - required bool initialValue, - required this.updateSearch, - required this.build, - }) : _value = initialValue; - - bool _value; - // ignore: avoid_positional_boolean_parameters - final void Function(bool newValue) updateSearch; - final Widget Function( - BuildContext context, { - required bool value, - required Function statefulOnChange, - }) build; - - set value(newValue) => _value = newValue; - bool get value => _value; -} - -class FilterMenu extends HookWidget { - const FilterMenu(this.cubit, this.state, this.activeDrugs, - {required this.searchForDrugClass}); - - final DrugListCubit cubit; - final DrugListState state; - final ActiveDrugs activeDrugs; - final bool searchForDrugClass; - - @override - Widget build(BuildContext context) { - return _buildFilters(context) ?? SizedBox.shrink(); - } - - Widget? _buildFilters(BuildContext context) { - return state.whenOrNull( - loaded: (allDrugs, filter) => SafeArea( - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: PharMeTheme.surfaceColor, - width: PharMeTheme.smallSpace, - ), - borderRadius: BorderRadius.only( - topRight: Radius.circular(PharMeTheme.smallSpace), - )), - child: Container( - color: PharMeTheme.surfaceColor, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only( - top: PharMeTheme.mediumSpace, - right: PharMeTheme.mediumSpace, - bottom: PharMeTheme.mediumSpace, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - IconButton( - key: Key('close-filter-drawer-button'), - onPressed: Scaffold.of(context).closeDrawer, - icon: Icon(Icons.arrow_back_ios_rounded), - color: PharMeTheme.iconColor, - visualDensity: VisualDensity.compact, - ), - SizedBox(width: PharMeTheme.smallSpace * 0.5), - Text( - context.l10n.search_page_filter_heading, - style: PharMeTheme.textTheme.headlineMedium, - ), - ], - ), - ), - Padding( - padding: EdgeInsets.only( - left: PharMeTheme.mediumSpace, - right: PharMeTheme.mediumSpace, - bottom: PharMeTheme.mediumSpace, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: PharMeTheme.mediumSpace), - SubheaderDivider( - text: context.l10n.search_page_filter_subheading_usage, - useLine: false, - padding: 0, - ), - _getDrugStatusFilter(context, allDrugs, filter), - SizedBox(height: PharMeTheme.mediumSpace), - SubheaderDivider( - text: context.l10n.search_page_filter_subheading_level, - useLine: false, - padding: 0, - ), - SizedBox(height: PharMeTheme.smallSpace), - ..._getWarningLevelFilters(context, allDrugs, filter), - ], - ), - ), - ], - ), - ), - ), - )); - } - - int _getFilteredNumber({ - required FilterState itemFilter, - required List drugs, - }) { - return itemFilter - .filter(drugs, activeDrugs, searchForDrugClass: searchForDrugClass) - .length; - } - - bool _filterIsEnabled({ - required FilterState itemFilter, - required List drugs, - }) => _getFilteredNumber(itemFilter: itemFilter, drugs: drugs) > 0; - - Widget _getFilterText( - String text, { - required FilterState itemFilter, - required List drugs, - bool enabled = true, - }) { - final numberTextColor = darkenColor(PharMeTheme.onSurfaceText, -0.2); - final disabledTextColor = darkenColor(numberTextColor, -0.2); - return Text.rich( - TextSpan(children: [ - TextSpan( - text: text, - style: PharMeTheme.textTheme.bodyMedium!.copyWith( - color: enabled - ? PharMeTheme.textTheme.bodyMedium!.color - : disabledTextColor, - ), - ), - TextSpan( - text: ' (${ - _getFilteredNumber(itemFilter: itemFilter, drugs: drugs) - })', - style: PharMeTheme.textTheme.labelMedium!.copyWith( - color: enabled ? numberTextColor : disabledTextColor, - ), - ), - ]), - ); - } - - Widget _getDrugStatusFilter( - BuildContext context, - List drugs, - FilterState filter, - ) { - final value = filter.showInactive; - FilterState drugStatusFilterState({ required bool showInactive }) { - return FilterState.from( - FilterState.initial(), - showInactive: showInactive, - ); - } - DropdownMenuItem buildDrugStatusDropdownItem({ - required bool showInactive, - }) { - final itemFilter = drugStatusFilterState(showInactive: showInactive); - final text = showInactive - ? context.l10n.search_page_filter_all_drugs - : context.l10n.search_page_filter_only_active_drugs; - final enabled = _filterIsEnabled(itemFilter: itemFilter, drugs: drugs); - return DropdownMenuItem( - key: Key('drug-status-filter-${showInactive.toString()}'), - value: showInactive, - enabled: enabled, - child: _getFilterText( - text, - itemFilter: itemFilter, - drugs: drugs, - enabled: enabled, - ), - ); - } - - return DropdownButton( - key: Key('drug-status-filter-dropdown'), - value: value, - items: [ - buildDrugStatusDropdownItem(showInactive: true), - buildDrugStatusDropdownItem(showInactive: false), - ], - onChanged: (newValue) => - newValue != value ? cubit.search(showInactive: newValue) : null, - ); - } - - List _getWarningLevelFilters( - BuildContext context, - List drugs, - FilterState filter, - ) { - FilterState warningLevelFilter(WarningLevel warningLevel) { - final currentFilter = FilterState.from(filter); - currentFilter.showWarningLevel.forEach( - (currentWarningLevel, currentValue) => - currentFilter.showWarningLevel[currentWarningLevel] = - currentWarningLevel == warningLevel); - return currentFilter; - } - Widget buildWarningLevelItem(WarningLevel warningLevel) { - final value = filter.showWarningLevel[warningLevel]!; - final itemFilter = warningLevelFilter(warningLevel); - final enabled = _filterIsEnabled(itemFilter: itemFilter, drugs: drugs); - return ActionChip( - onPressed: enabled - ? () => cubit.search( - showWarningLevel: {warningLevel: !value}, - ) - : null, - avatar: Icon( - value && enabled ? warningLevel.icon : warningLevel.outlinedIcon, - color: PharMeTheme.onSurfaceText, - ), - label: _getFilterText( - warningLevel.getLabel(context), - itemFilter: itemFilter, - drugs: drugs, - ), - visualDensity: VisualDensity.compact, - color: MaterialStatePropertyAll( - value && enabled ? warningLevel.color : Colors.transparent, - ), - side: BorderSide( - color: value && enabled - ? warningLevel.color - : PharMeTheme.onSurfaceColor, - ), - ); - } - return WarningLevel.values.map(buildWarningLevelItem).toList(); - } -} diff --git a/app/lib/common/widgets/module.dart b/app/lib/common/widgets/module.dart index c5f35238..c36a7ed9 100644 --- a/app/lib/common/widgets/module.dart +++ b/app/lib/common/widgets/module.dart @@ -10,9 +10,7 @@ export 'drug_list/cubit.dart'; export 'drug_list/drug_items/drug_cards.dart'; export 'drug_list/drug_items/drug_selection_list.dart'; export 'drug_search/builder.dart'; -export 'drug_search/filter_button.dart'; export 'drug_search/filter_data_wrapper.dart'; -export 'drug_search/filter_menu.dart'; export 'error_handler.dart'; export 'full_width_button.dart'; export 'headings.dart'; diff --git a/app/lib/common/widgets/page_scaffold.dart b/app/lib/common/widgets/page_scaffold.dart index f2428513..ff30215f 100644 --- a/app/lib/common/widgets/page_scaffold.dart +++ b/app/lib/common/widgets/page_scaffold.dart @@ -30,11 +30,11 @@ Widget buildTitle(String text, { String? tooltipText }) { } Scaffold pageScaffold({ - required String title, required List body, + required String title, List? actions, - Key? key, bool canNavigateBack = true, + Key? key, }) { return Scaffold( key: key, @@ -67,7 +67,6 @@ Scaffold unscrollablePageScaffold({ String? title, String? titleTooltip, List? actions, - Widget? drawer, bool canNavigateBack = true, Key? key, }) { @@ -94,6 +93,5 @@ Scaffold unscrollablePageScaffold({ child: body, ), ), - drawer: drawer, ); } diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index a3dec2ac..5c76f13e 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -58,11 +58,7 @@ "search_page_tooltip_search": "Search for medications by their name, brand name or class.", "search_page_tooltip_search_no_class": "Search for medications by their name or brand name.", - "search_page_filter_heading": "Filter medications", - "search_page_filter_subheading_usage": "Filter by usage status", - "search_page_filter_subheading_level": "Filter by guideline result", - "search_page_filter_all_drugs": "All medications", - "search_page_filter_only_active_drugs": "Current medications", + "search_page_filter_label": "Filter", "search_page_indicator_explanation": "Taking medications with an {indicatorName} ({indicator}) can interact with your results for other medications", "@search_page_indicator_explanation": { "placeholders": { diff --git a/app/lib/search/pages/search.dart b/app/lib/search/pages/search.dart index 8f139ad8..3fedca59 100644 --- a/app/lib/search/pages/search.dart +++ b/app/lib/search/pages/search.dart @@ -34,12 +34,6 @@ class SearchPage extends HookWidget { searchForDrugClass: searchForDrugClass, showDrugInteractionIndicator: false, ), - drawer: FilterMenu( - cubit, - state, - activeDrugs, - searchForDrugClass: searchForDrugClass, - ), ), ), );