From 118e2392a5e9e5a57f16d09ab566aa0146a098f1 Mon Sep 17 00:00:00 2001 From: Tamara Slosarek Date: Thu, 22 Feb 2024 16:00:12 +0100 Subject: [PATCH] feat(#691): add numbers behind filter menu items * Moves withFilterData up to fix that cubit is closed when closing the drawer * Refactors check whether to get lookups for better debugging --- app/lib/common/utilities/genome_data.dart | 11 +- .../common/widgets/drug_search/builder.dart | 96 ++++++------ .../drug_search/filter_data_wrapper.dart | 20 +++ .../widgets/drug_search/filter_menu.dart | 143 +++++++++++------- app/lib/common/widgets/module.dart | 1 + .../drug_selection/pages/drug_selection.dart | 32 ++-- app/lib/search/pages/search.dart | 32 ++-- 7 files changed, 198 insertions(+), 137 deletions(-) create mode 100644 app/lib/common/widgets/drug_search/filter_data_wrapper.dart diff --git a/app/lib/common/utilities/genome_data.dart b/app/lib/common/utilities/genome_data.dart index f4aa9ada..cebe119b 100644 --- a/app/lib/common/utilities/genome_data.dart +++ b/app/lib/common/utilities/genome_data.dart @@ -73,16 +73,13 @@ Future updateGenotypeResults() async { bool shouldUpdateGenotypeResults() { final genotypeResultsPresent = UserData.instance.labData?.isNotEmpty ?? false; + final lookupsAreOutdated = MetaData.instance.lookupsLastFetchDate == null || + DateTime.now().difference(MetaData.instance.lookupsLastFetchDate!) > + cpicMaxCacheTime; final labDataPresent = UserData.instance.labData?.isNotEmpty ?? false; - return (!genotypeResultsPresent || _isOutDated()) && labDataPresent; + return labDataPresent && (!genotypeResultsPresent || lookupsAreOutdated); } bool shouldFetchDiplotypes() { return UserData.instance.labData == null; } - -bool _isOutDated() { - final lastFetchDate = MetaData.instance.lookupsLastFetchDate; - if (lastFetchDate == null) return true; - return DateTime.now().difference(lastFetchDate) > cpicMaxCacheTime; -} diff --git a/app/lib/common/widgets/drug_search/builder.dart b/app/lib/common/widgets/drug_search/builder.dart index 16df703c..c2bf2f3f 100644 --- a/app/lib/common/widgets/drug_search/builder.dart +++ b/app/lib/common/widgets/drug_search/builder.dart @@ -1,21 +1,22 @@ import 'package:flutter/cupertino.dart'; -import 'package:provider/provider.dart'; import '../../../../common/module.dart'; import '../../../drug/widgets/tooltip_icon.dart'; class DrugSearch extends HookWidget { - DrugSearch({ + const DrugSearch({ super.key, required this.showFilter, required this.buildDrugItems, required this.showDrugInteractionIndicator, - this.useDrugClass = true, + required this.useDrugClass, + required this.cubit, + required this.state, + required this.activeDrugs, this.keepPosition = false, this.drugItemsBuildParams, - DrugListCubit? cubit, - }) : cubit = cubit ?? DrugListCubit(); + }); final bool showFilter; final bool useDrugClass; @@ -30,57 +31,50 @@ class DrugSearch extends HookWidget { ) buildDrugItems; final bool showDrugInteractionIndicator; final DrugListCubit cubit; + final DrugListState state; + final ActiveDrugs activeDrugs; final Map? drugItemsBuildParams; @override Widget build(BuildContext context) { final searchController = useTextEditingController(); - return Consumer( - builder: (context, activeDrugs, child) => BlocProvider( - create: (context) => cubit, - child: BlocBuilder( - builder: (context, state) { - return Column( - children: [ - Padding( - padding: EdgeInsets.only( - left: PharMeTheme.smallSpace, - right: PharMeTheme.smallSpace, - bottom: PharMeTheme.smallSpace, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - ..._buildSearchBarItems(context, searchController), - if (showFilter) FilterButton(), - ], - ), - ), - scrollList( - keepPosition: keepPosition, - buildDrugList( - context, - state, - activeDrugs, - buildDrugItems: buildDrugItems, - noDrugsMessage: context.l10n.search_no_drugs( - showFilter - ? context.l10n.search_no_drugs_with_filter_amendment - : '' - ), - drugItemsBuildParams: drugItemsBuildParams, - showDrugInteractionIndicator: - showDrugInteractionIndicator, - useDrugClass: useDrugClass, - ) - ), - _maybeBuildInteractionIndicator(context, state, activeDrugs) - ?? SizedBox.shrink(), - ], - ); - } - ) - ) + return Column( + children: [ + Padding( + padding: EdgeInsets.only( + left: PharMeTheme.smallSpace, + right: PharMeTheme.smallSpace, + bottom: PharMeTheme.smallSpace, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ..._buildSearchBarItems(context, searchController), + if (showFilter) FilterButton(), + ], + ), + ), + scrollList( + keepPosition: keepPosition, + buildDrugList( + context, + state, + activeDrugs, + buildDrugItems: buildDrugItems, + noDrugsMessage: context.l10n.search_no_drugs( + showFilter + ? context.l10n.search_no_drugs_with_filter_amendment + : '' + ), + drugItemsBuildParams: drugItemsBuildParams, + showDrugInteractionIndicator: + showDrugInteractionIndicator, + useDrugClass: useDrugClass, + ) + ), + _maybeBuildInteractionIndicator(context, state, activeDrugs) + ?? SizedBox.shrink(), + ], ); } diff --git a/app/lib/common/widgets/drug_search/filter_data_wrapper.dart b/app/lib/common/widgets/drug_search/filter_data_wrapper.dart new file mode 100644 index 00000000..dc71293c --- /dev/null +++ b/app/lib/common/widgets/drug_search/filter_data_wrapper.dart @@ -0,0 +1,20 @@ +import 'package:provider/provider.dart'; + +import '../../module.dart'; + +Widget withFilterData({ + required DrugListCubit cubit, + required Widget Function( + BuildContext context, + DrugListCubit cubit, + DrugListState state, + ActiveDrugs activeDrugs, + ) builder, +}) => Consumer( + builder: (context, activeDrugs, child) => BlocProvider( + create: (context) => cubit, + child: BlocBuilder( + builder: (context, state) => builder(context, cubit, state, activeDrugs), + ), + ), +); \ No newline at end of file diff --git a/app/lib/common/widgets/drug_search/filter_menu.dart b/app/lib/common/widgets/drug_search/filter_menu.dart index 5347c97d..78d5233b 100644 --- a/app/lib/common/widgets/drug_search/filter_menu.dart +++ b/app/lib/common/widgets/drug_search/filter_menu.dart @@ -29,73 +29,104 @@ class FilterMenuItem { set value(newValue) => _value = newValue; bool get value => _value; } - class FilterMenu extends HookWidget { - const FilterMenu(this.cubit); + const FilterMenu(this.cubit, this.state, this.activeDrugs, { + required this.useDrugClass + }); final DrugListCubit cubit; + final DrugListState state; + final ActiveDrugs activeDrugs; + final bool useDrugClass; @override Widget build(BuildContext context) { - return SafeArea( - child: Column( - children: _menuItems.map( - (item) => PopupMenuItem(child: StatefulBuilder( - builder: (context, setState) { - return item.build( - context, - value: item.value, - statefulOnChange: ([_]) { - final newValue = !item.value; - setState(() => item.value = newValue); - item.updateSearch(newValue); - }, - ); - }), - ), - ).toList(), - ), + return _buildFilters(context) ?? SizedBox.shrink(); + } + + Widget? _buildFilters(BuildContext context) { + return state.whenOrNull(loaded: (allDrugs, filter) => + SafeArea( + child: Column( + children: _geMenuItems(context, allDrugs, filter, activeDrugs), + ), + ) ); } - List get _menuItems => [ - FilterMenuItem( - initialValue: cubit.filter?.showInactive ?? false, - updateSearch: (newValue) => cubit.search(showInactive: newValue), - build: (context, { required value, required statefulOnChange }) => - DropdownButton( - value: value, - items: [ - DropdownMenuItem( - value: true, - child: Text('${context.l10n.search_page_filter_all_drugs} '), + List _geMenuItems( + BuildContext context, + List drugs, + FilterState filter, + ActiveDrugs activeDrugs, + ) { + String formatItemFilterNumber(FilterState itemFilter) => + '(${ + itemFilter.filter(drugs, activeDrugs, useDrugClass: useDrugClass).length + })'; + String drugStatusNumber({ required bool showInactive}) => + formatItemFilterNumber(FilterState.from( + FilterState.initial(), + showInactive: showInactive, + )); + String warningLevelNumber(WarningLevel warningLevel) { + final currentWarningLevelFilter = FilterState.from(filter); + currentWarningLevelFilter.showWarningLevel.forEach( + (currentWarningLevel, currentValue) => + currentWarningLevelFilter.showWarningLevel[currentWarningLevel] = + currentWarningLevel == warningLevel + ); + return formatItemFilterNumber(currentWarningLevelFilter); + } + Widget buildDrugStatusItem() { + final value = filter.showInactive; + return DropdownButton( + value: value, + items: [ + DropdownMenuItem( + value: true, + child: Text( + '${context.l10n.search_page_filter_all_drugs} ' + '${drugStatusNumber(showInactive: true)}' ), - DropdownMenuItem( - value: false, - child: Text('${context.l10n.search_page_filter_only_active_drugs} '), + ), + DropdownMenuItem( + value: false, + child: Text( + '${context.l10n.search_page_filter_only_active_drugs} ' + '${drugStatusNumber(showInactive: false)}' ), - ], - onChanged: (newValue) => statefulOnChange(newValue), + ), + ], + onChanged: (newValue) => newValue != value + ? cubit.search(showInactive: newValue) + : null, + ); + } + Widget buildWarningLevelItem(WarningLevel warningLevel) { + final value = filter.showWarningLevel[warningLevel]!; + return ActionChip( + onPressed: () => cubit.search( + showWarningLevel: { warningLevel: !value }, ), - ), - ...WarningLevel.values - .filter((warningLevel) => warningLevel != WarningLevel.none) - .map((warningLevel) => FilterMenuItem( - initialValue: cubit.filter?.showWarningLevel[warningLevel] ?? false, - updateSearch: (newValue) => cubit.search( - showWarningLevel: { warningLevel: newValue }, + avatar: Icon( + value ? warningLevel.icon : warningLevel.outlinedIcon, + color: value ? PharMeTheme.onSurfaceText : warningLevel.textColor, ), - build: (context, { required value, required statefulOnChange }) => - ActionChip( - onPressed: () => statefulOnChange(!value), - avatar: Icon( - value ? warningLevel.icon : warningLevel.outlinedIcon, - color: value ? PharMeTheme.onSurfaceText : warningLevel.textColor, - ), - label: Text('', style: TextStyle(color: PharMeTheme.onSurfaceText)), - visualDensity: VisualDensity.compact, - color: MaterialStatePropertyAll(value ? warningLevel.color : Colors.transparent), - ), - )), - ]; + label: Text(warningLevelNumber(warningLevel), + style: TextStyle(color: PharMeTheme.onSurfaceText)), + visualDensity: VisualDensity.compact, + color: MaterialStatePropertyAll(value + ? warningLevel.color + : Colors.transparent + ), + ); + } + return [ + buildDrugStatusItem(), + ...WarningLevel.values + .filter((warningLevel) => warningLevel != WarningLevel.none) + .map(buildWarningLevelItem), + ]; + } } diff --git a/app/lib/common/widgets/module.dart b/app/lib/common/widgets/module.dart index a166e872..26ef9023 100644 --- a/app/lib/common/widgets/module.dart +++ b/app/lib/common/widgets/module.dart @@ -8,6 +8,7 @@ export 'drug_list/builder.dart'; export 'drug_list/cubit.dart'; export 'drug_list/drug_items/drug_cards.dart'; export 'drug_search/builder.dart'; +export 'drug_search/filter_data_wrapper.dart'; export 'drug_search/filter_menu.dart'; export 'error_handler.dart'; export 'full_width_button.dart'; diff --git a/app/lib/drug_selection/pages/drug_selection.dart b/app/lib/drug_selection/pages/drug_selection.dart index 160d42e7..38bee817 100644 --- a/app/lib/drug_selection/pages/drug_selection.dart +++ b/app/lib/drug_selection/pages/drug_selection.dart @@ -37,7 +37,7 @@ class DrugSelectionPage extends HookWidget { ); } ), - ) + ), ); } @@ -77,18 +77,24 @@ class DrugSelectionPage extends HookWidget { ], ); } - return DrugSearch( - showFilter: false, - keepPosition: true, - useDrugClass: false, - buildDrugItems: buildDrugCheckboxList, - drugItemsBuildParams: { - 'checkboxesEnabled': _isEditable(state), - 'onCheckboxChange': (drug, value) => context - .read() - .updateDrugActivity(drug, value), - }, - showDrugInteractionIndicator: false, + return withFilterData( + cubit: DrugListCubit(), + builder: (context, builderCubit, builderState, activeDrugs) => DrugSearch( + showFilter: false, + cubit: builderCubit, + state: builderState, + activeDrugs: activeDrugs, + keepPosition: true, + useDrugClass: false, + buildDrugItems: buildDrugCheckboxList, + drugItemsBuildParams: { + 'checkboxesEnabled': _isEditable(state), + 'onCheckboxChange': (drug, value) => context + .read() + .updateDrugActivity(drug, value), + }, + showDrugInteractionIndicator: false, + ), ); } } diff --git a/app/lib/search/pages/search.dart b/app/lib/search/pages/search.dart index 8062996e..71ded84d 100644 --- a/app/lib/search/pages/search.dart +++ b/app/lib/search/pages/search.dart @@ -16,17 +16,29 @@ class SearchPage extends HookWidget { await cubit.loadDrugs(useCache: false); } }); - return PopScope( - canPop: false, - child: unscrollablePageScaffold( - title: context.l10n.tab_drugs, - body: DrugSearch( - showFilter: true, - buildDrugItems: buildDrugCards, - cubit: cubit, - showDrugInteractionIndicator: true, + const useDrugClass = true; + return withFilterData( + cubit: cubit, + builder: (context, cubit, state, activeDrugs) => PopScope( + canPop: false, + child: unscrollablePageScaffold( + title: context.l10n.tab_drugs, + body: DrugSearch( + showFilter: true, + buildDrugItems: buildDrugCards, + cubit: cubit, + state: state, + activeDrugs: activeDrugs, + showDrugInteractionIndicator: true, + useDrugClass: useDrugClass, + ), + drawer: FilterMenu( + cubit, + state, + activeDrugs, + useDrugClass: useDrugClass, + ), ), - drawer: FilterMenu(cubit), ), ); }