diff --git a/app/lib/common/models/drug/drug.dart b/app/lib/common/models/drug/drug.dart index 64f36920..ce6361a1 100644 --- a/app/lib/common/models/drug/drug.dart +++ b/app/lib/common/models/drug/drug.dart @@ -2,7 +2,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive/hive.dart'; import '../../module.dart'; -import '../../utilities/guideline_utils.dart'; part 'drug.g.dart'; @@ -40,7 +39,7 @@ class Drug { List guidelines; @override - bool operator ==(other) => other is Drug && id == other.id; + bool operator == (other) => other is Drug && id == other.id; @override int get hashCode => id.hashCode; @@ -67,13 +66,10 @@ class DrugAnnotations { List brandNames; } -extension DrugIsActive on Drug { - bool isActive() { - return UserData.instance.activeDrugNames?.contains(name) ?? false; - } -} +extension DrugExtension on Drug { + bool get isActive => + UserData.instance.activeDrugNames?.contains(name) ?? false; -extension DrugMatchesQuery on Drug { bool matches({required String query, required bool useClass}) { final namesMatch = name.ilike(query) || (annotations.brandNames.any((brand) => brand.ilike(query))); @@ -81,34 +77,28 @@ extension DrugMatchesQuery on Drug { ? namesMatch || (annotations.drugclass.ilike(query)) : namesMatch; } -} -/// Gets the User's matching guideline -extension DrugWithUserGuideline on Drug { - Guideline? userGuideline() => guidelines.firstOrNullWhere( - (guideline) => guideline.lookupkey.all((geneSymbol, geneResults) => - geneResults.contains(UserData.lookupFor(geneSymbol, drug: name))), - ); -} + Guideline? get userGuideline => guidelines.firstOrNullWhere( + (guideline) => guideline.lookupkey.all( + (geneSymbol, geneResults) => + geneResults.contains(UserData.lookupFor(geneSymbol, drug: name)) + ), + ); -extension DrugGuidelineGenes on Drug { List get guidelineGenes => guidelines.isNotEmpty ? guidelines.first.lookupkey.keys.toList() : []; -} -extension DrugWarningLevel on Drug { WarningLevel get warningLevel => - userGuideline()?.annotations.warningLevel ?? WarningLevel.none; + userGuideline?.annotations.warningLevel ?? WarningLevel.none; } /// Filters for drugs with non-OK warning level extension CriticalDrugs on List { List filterCritical() { return filter((drug) { - final warningLevel = getWarningLevel(drug.userGuideline()); - return warningLevel != WarningLevel.none && - warningLevel != WarningLevel.green; + return drug.warningLevel != WarningLevel.none && + drug.warningLevel != WarningLevel.green; }).toList(); } } diff --git a/app/lib/common/pages/drug/drug.dart b/app/lib/common/pages/drug/drug.dart index ec2ea218..1ffed21d 100644 --- a/app/lib/common/pages/drug/drug.dart +++ b/app/lib/common/pages/drug/drug.dart @@ -31,8 +31,6 @@ class DrugPage extends StatelessWidget { } Widget _buildDrugsPage(BuildContext context, { required bool loading }) { - final userGuideline = drug.userGuideline(); - final isActive = drug.isActive(); return pageScaffold( title: drug.name.capitalize(), actions: [ @@ -55,7 +53,7 @@ class DrugPage extends StatelessWidget { SizedBox(height: 12), DrugAnnotationCard( drug, - isActive: isActive, + isActive: drug.isActive, setActivity: (value) => context.read().setActivity(drug, value), disabled: loading, @@ -66,11 +64,11 @@ class DrugPage extends StatelessWidget { tooltip: context.l10n.drugs_page_tooltip_guideline, ), SizedBox(height: 12), - if (userGuideline != null) ...[ + if (drug.userGuideline != null) ...[ Disclaimer(), SizedBox(height: 12), ], - GuidelineAnnotationCard(userGuideline, drug), + GuidelineAnnotationCard(drug), ], ), ), diff --git a/app/lib/common/pages/drug/widgets/annotation_cards/guideline.dart b/app/lib/common/pages/drug/widgets/annotation_cards/guideline.dart index 893c3b63..9eb3b6c3 100644 --- a/app/lib/common/pages/drug/widgets/annotation_cards/guideline.dart +++ b/app/lib/common/pages/drug/widgets/annotation_cards/guideline.dart @@ -1,14 +1,12 @@ import 'package:url_launcher/url_launcher.dart'; import '../../../../module.dart'; -import '../../../../utilities/guideline_utils.dart'; import '../sub_header.dart'; class GuidelineAnnotationCard extends StatelessWidget { - const GuidelineAnnotationCard(this.guideline, this.drug); + const GuidelineAnnotationCard(this.drug); - final Guideline? guideline; - final Drug? drug; + final Drug drug; @override Widget build(BuildContext context) { @@ -18,7 +16,7 @@ class GuidelineAnnotationCard extends StatelessWidget { child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(context), SizedBox(height: 12), - if (guideline != null) ...[ + if (drug.userGuideline != null) ...[ _buildCard(context), SizedBox(height: 8), Divider(color: PharMeTheme.borderColor), @@ -36,25 +34,27 @@ class GuidelineAnnotationCard extends StatelessWidget { } Widget _buildCard(BuildContext context) { - final warningLevel = getWarningLevel(guideline); - final upperCardText = guideline?.annotations.implication ?? + final upperCardText = drug.userGuideline?.annotations.implication ?? context.l10n.drugs_page_no_guidelines_for_phenotype_implication( - drug!.name + drug.name ); - final lowerCardText = guideline?.annotations.recommendation ?? + final lowerCardText = drug.userGuideline?.annotations.recommendation ?? context.l10n.drugs_page_no_guidelines_for_phenotype_recommendation; return Card( key: Key('annotationCard'), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), - color: warningLevel.color, + color: drug.warningLevel.color, child: Padding( padding: EdgeInsets.all(12), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Icon(warningLevel.icon, color: PharMeTheme.onSurfaceText), + Icon( + drug.warningLevel.icon, + color: PharMeTheme.onSurfaceText, + ), SizedBox(width: 12), Flexible( child: Text( @@ -74,17 +74,17 @@ class GuidelineAnnotationCard extends StatelessWidget { Widget _buildHeader(BuildContext context) { var headerContent = ''; var headerStyle = PharMeTheme.textTheme.bodyLarge!; - if (guideline == null && drug!.guidelines.isEmpty) { - headerContent = context.l10n.drugs_page_guidelines_empty(drug!.name); + if (drug.userGuideline == null && drug.guidelines.isEmpty) { + headerContent = context.l10n.drugs_page_guidelines_empty(drug.name); headerStyle = headerStyle.copyWith(fontStyle: FontStyle.italic); } else { - final genes = guideline?.lookupkey.keys ?? - drug!.guidelines.first.lookupkey.keys; + final genes = drug.userGuideline?.lookupkey.keys ?? + drug.guidelines.first.lookupkey.keys; final geneDescriptions = genes.map((geneSymbol) { final phenotypeInformation = UserData.phenotypeFor( geneSymbol, context, - drug: drug?.name, + drug: drug.name, ); var description = '$geneSymbol: ${phenotypeInformation.phenotype}'; if (phenotypeInformation.adaptionText.isNotNullOrBlank) { @@ -108,7 +108,7 @@ class GuidelineAnnotationCard extends StatelessWidget { // pipes are illegal characters in URLs so please // - forgive the cheap hack or // - refactor by making a custom object and defining equality for it :) - final sources = guideline!.externalData + final sources = drug.userGuideline!.externalData .map((data) => '${data.source}|${data.guidelineUrl}') .toSet(); return Column(children: [ diff --git a/app/lib/common/utilities/guideline_utils.dart b/app/lib/common/utilities/guideline_utils.dart deleted file mode 100644 index 06da81ed..00000000 --- a/app/lib/common/utilities/guideline_utils.dart +++ /dev/null @@ -1,15 +0,0 @@ -import '../models/drug/cached_drugs.dart'; -import '../module.dart'; - -List getGuidelineGenes() { - final genes = {}; - for (final drug in CachedDrugs.instance.drugs!) { - for (final guideline in drug.guidelines) { - guideline.lookupkey.keys.forEach(genes.add); - } - } - return List.from(genes); -} - -WarningLevel getWarningLevel(Guideline? guideline) => - guideline?.annotations.warningLevel ?? WarningLevel.none; \ No newline at end of file diff --git a/app/lib/common/utilities/pdf_utils.dart b/app/lib/common/utilities/pdf_utils.dart index 39319a6d..4d47b459 100644 --- a/app/lib/common/utilities/pdf_utils.dart +++ b/app/lib/common/utilities/pdf_utils.dart @@ -169,8 +169,7 @@ String _userInfoPerGene( BuildContext buildContext, ) { if (drug.guidelines.isEmpty) return buildContext.l10n.pdf_no_value; - final guidelineGenes = drug.guidelines.first.lookupkey.keys.toList(); - return guidelineGenes.map((gene) => + return drug.guidelineGenes.map((gene) => '$gene: ${ getInfo(gene, drug, buildContext) ?? buildContext.l10n.pdf_no_value }' @@ -183,7 +182,6 @@ List _buildUserPart( Drug drug, Font emoji, ) { - final userGuideline = drug.userGuideline(); final patientGenotype = _userInfoPerGene( drug, (gene, drug, context) => UserData.genotypeFor(gene), @@ -210,11 +208,11 @@ List _buildUserPart( 'green': '✅', null: '❔', }; - final implication = userGuideline?.annotations.implication ?? + final implication = drug.userGuideline?.annotations.implication ?? buildContext.l10n.drugs_page_no_guidelines_for_phenotype_implication( drug.name ); - final recommendation = userGuideline?.annotations.recommendation ?? + final recommendation = drug.userGuideline?.annotations.recommendation ?? buildContext.l10n.drugs_page_no_guidelines_for_phenotype_recommendation; return [ _buildSubheading(buildContext.l10n.pdf_heading_user_data), @@ -251,7 +249,7 @@ List _buildUserPart( child: _PdfDescription( title: buildContext.l10n.pdf_user_guideline, text: '$implication $recommendation', - icon: warningLevelIcons[userGuideline?.annotations.warningLevel.name], + icon: warningLevelIcons[drug.warningLevel.name], emojiFont: emoji, ), ), @@ -262,7 +260,7 @@ List _buildExternalGuidelinePart( Drug drug, BuildContext buildContext ) { - final externalData = drug.userGuideline()?.externalData; + final externalData = drug.userGuideline?.externalData; final heading = _buildSubheading( buildContext.l10n.pdf_heading_clinical_guidelines ); diff --git a/app/lib/common/widgets/drug_list/cubit.dart b/app/lib/common/widgets/drug_list/cubit.dart index d5aeae22..e94be55e 100644 --- a/app/lib/common/widgets/drug_list/cubit.dart +++ b/app/lib/common/widgets/drug_list/cubit.dart @@ -125,16 +125,10 @@ class FilterState { bool isAccepted(Drug drug, ActiveDrugs activeDrugs, { required bool useDrugClass, }) { - final userGuideline = drug.userGuideline(); - final guidelineGenes = drug.guidelines.isNotEmpty ? - drug.guidelines.first.lookupkey.keys.toList() : - []; - final warningLevel = - userGuideline?.annotations.warningLevel ?? WarningLevel.none; - var warningLevelMatches = showWarningLevel[warningLevel] ?? true; + var warningLevelMatches = showWarningLevel[drug.warningLevel] ?? true; // WarningLevel.none is also shown in green in app; therefore, it should // also be filtered with green option - if (warningLevel == WarningLevel.none) { + if (drug.warningLevel == WarningLevel.none) { warningLevelMatches = warningLevelMatches && showWarningLevel[WarningLevel.green]!; } @@ -142,7 +136,7 @@ class FilterState { drug.matches(query: query, useClass: useDrugClass) && (activeDrugs.contains(drug.name) || showInactive) && warningLevelMatches && - (gene.isBlank || (guidelineGenes.contains(gene))); + (gene.isBlank || (drug.guidelineGenes.contains(gene))); return isDrugAccepted; } diff --git a/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart b/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart index 63be6e1b..f44e850a 100644 --- a/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart +++ b/app/lib/common/widgets/drug_list/drug_items/drug_cards.dart @@ -1,5 +1,4 @@ import '../../../module.dart'; -import '../../../utilities/guideline_utils.dart'; import 'utils.dart'; List buildDrugCards( @@ -10,11 +9,9 @@ List buildDrugCards( bool showDrugInteractionIndicator = false, } ) { - int warningLevelSeverity(Drug drug) => - getWarningLevel(drug.userGuideline()).severity; drugs.sort((drugA, drugB) { - final warningLevelComparison = -warningLevelSeverity(drugA) - .compareTo(warningLevelSeverity(drugB)); + final warningLevelComparison = -drugA.warningLevel.severity + .compareTo(drugB.warningLevel.severity); if (warningLevelComparison == 0) { return drugA.name.compareTo(drugB.name); } @@ -45,7 +42,6 @@ class DrugCard extends StatelessWidget { @override Widget build(BuildContext context) { - final warningLevel = getWarningLevel(drug.userGuideline()); final drugName = formatDrugName(drug, showDrugInteractionIndicator); return Padding( padding: EdgeInsets.symmetric(vertical: PharMeTheme.smallSpace / 2), @@ -53,7 +49,7 @@ class DrugCard extends StatelessWidget { onTap: onTap, padding: EdgeInsets.all(8), radius: 16, - color: warningLevel.color, + color: drug.warningLevel.color, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -62,7 +58,7 @@ class DrugCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Icon(warningLevel.icon), + Icon(drug.warningLevel.icon), SizedBox(width: 4), Text( drugName, diff --git a/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart b/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart index b178234c..cbffffd5 100644 --- a/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart +++ b/app/lib/common/widgets/drug_list/drug_items/drug_checkbox_list.dart @@ -10,7 +10,7 @@ List buildDrugCheckboxList( } ) { if (buildParams == null) throw Exception(); - final activeDrugs = drugs.filter((drug) => drug.isActive()).toList(); + final activeDrugs = drugs.filter((drug) => drug.isActive).toList(); final activeDrugsList = activeDrugs.isEmpty ? [Padding( padding: EdgeInsets.all(PharMeTheme.mediumSpace), @@ -57,7 +57,7 @@ List _buildCheckboxList( (drug) => CheckboxListTile( key: Key('drug-checkbox-tile-${drug.name}-$keyPrefix'), enabled: checkboxesEnabled, - value: drug.isActive(), + value: drug.isActive, onChanged: (value) => onCheckboxChange(drug, value), title: Text(formatDrugName(drug, showDrugInteractionIndicator)), subtitle: (drug.annotations.brandNames.isNotEmpty) ? diff --git a/app/lib/report/pages/report.dart b/app/lib/report/pages/report.dart index 706e201f..452e68bb 100644 --- a/app/lib/report/pages/report.dart +++ b/app/lib/report/pages/report.dart @@ -3,7 +3,6 @@ import 'package:provider/provider.dart'; import '../../common/models/drug/cached_drugs.dart'; import '../../common/module.dart'; import '../../common/utilities/color_utils.dart'; -import '../../common/utilities/guideline_utils.dart'; class ReportPage extends StatelessWidget { @override @@ -16,10 +15,15 @@ class ReportPage extends StatelessWidget { Widget _buildReportPage(BuildContext context, ActiveDrugs activeDrugs) { final hasActiveInhibitors = activeDrugs.names.any(isInhibitor); - final guidelineGenes = getGuidelineGenes(); + final genes = {}; + for (final drug in CachedDrugs.instance.drugs!) { + for (final guideline in drug.guidelines) { + guideline.lookupkey.keys.forEach(genes.add); + } + } final notTestedString = context.l10n.general_not_tested; - final userPhenotypes = guidelineGenes.map( + final userPhenotypes = genes.map( (geneSymbol) => UserData.instance.lookups![geneSymbol] ?? CpicPhenotype( geneSymbol: geneSymbol,