Skip to content

Commit

Permalink
feat(app): use expansion tile on gene report
Browse files Browse the repository at this point in the history
  • Loading branch information
tamslo committed Jan 13, 2025
1 parent 356380c commit 6b24160
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 109 deletions.
2 changes: 1 addition & 1 deletion app/integration_test/report_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void main() {
home: Scaffold(
body: Builder(
builder: (context) {
return ReportPage(onlyShowWholeReport: true);
return ReportPage(allGenesInitiallyExpanded: true);
},
),
),
Expand Down
8 changes: 6 additions & 2 deletions app/lib/common/widgets/subheader_divider.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import '../module.dart';

TextStyle subheaderDividerStyle({Color? color}) =>
PharMeTheme.textTheme.bodySmall!.copyWith(
color: color ?? PharMeTheme.subheaderColor,
);

class SubheaderDivider extends StatelessWidget {
const SubheaderDivider({
this.text = '',
Expand All @@ -25,8 +30,7 @@ class SubheaderDivider extends StatelessWidget {
if (useLine) Divider(color: widgetColor, thickness: 0.5),
Text(
text,
style:
PharMeTheme.textTheme.bodySmall!.copyWith(color: widgetColor),
style: subheaderDividerStyle(color: widgetColor),
textAlign: TextAlign.start,
),
],
Expand Down
15 changes: 12 additions & 3 deletions app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@
"@report_current_medications": {},
"report_all_medications": "all known medication interactions",
"@report_all_medications": {},
"report_gene_number": "{geneNumber, plural, =1{ (1 gene)} other{ ({geneNumber} genes)}}",
"report_gene_number": "{geneNumber, plural, =1{1 gene} other{{geneNumber} genes}}",
"@report_gene_number": {
"placeholders": {
"geneNumber": {
Expand All @@ -302,6 +302,15 @@
}
}
},
"report_medication_number": "{medicationNumber, plural, =1{1 medication} other{{medicationNumber} medications}}",
"@report_medication_number": {
"placeholders": {
"medicationNumber": {
"type": "int",
"example": "3"
}
}
},
"show_all_dropdown_text": "Having trouble finding a {item}? Use the dropdown button (▾) {position} to show all {items}.",
"@show_all_dropdown_text": {
"placeholders": {
Expand All @@ -315,11 +324,11 @@
},
"items": {
"type": "String",
"example": "genes with known medication interactions"
"example": "medications with clinically relevant gene interactions"
}
}
},
"report_dropdown_position": "above",
"report_dropdown_position": "below the current medication interaction report",
"@report_dropdown_position": {},
"medications_dropdown_position": "below the current medications",
"@medications_dropdown_position": {},
Expand Down
233 changes: 130 additions & 103 deletions app/lib/report/pages/report.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,6 @@ import 'package:provider/provider.dart';
import '../../common/module.dart';

typedef WarningLevelCounts = Map<WarningLevel, int>;
class ListOption {
ListOption({required this.label, this.drugSubset});
Widget getDescription(BuildContext context, int geneNumber) {
return Text.rich(
style: PharMeTheme.textTheme.labelLarge,
TextSpan(
children: [
TextSpan(text: context.l10n.report_description_prefix),
TextSpan(text: ' '),
TextSpan(text: label, style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text: context.l10n.report_gene_number(geneNumber),
style: TextStyle(color: PharMeTheme.buttonColor),
),
],
),
);
}
final String label;
final List<String>? drugSubset;
}

enum SortOption {
alphabetical,
Expand All @@ -33,22 +12,21 @@ enum SortOption {

@RoutePage()
class ReportPage extends HookWidget {
const ReportPage({@visibleForTesting this.onlyShowWholeReport = false});
const ReportPage({@visibleForTesting this.allGenesInitiallyExpanded = false});

// Currently for testing but might use in the future
final bool onlyShowWholeReport;
final bool allGenesInitiallyExpanded;

@override
Widget build(BuildContext context) {
final currentListOption = useState(0);
// Not used yet, but could be adaptable in the future
final allGenesExpanded = useState(allGenesInitiallyExpanded);
// Not changeable yet in UI!
final currentSortOption = useState(SortOption.alphabetical);
return Consumer<ActiveDrugs>(
builder: (context, activeDrugs, child) =>
_buildReportPage(
context,
activeDrugs,
currentListOption,
allGenesExpanded,
currentSortOption,
)
);
Expand Down Expand Up @@ -76,24 +54,28 @@ class ReportPage extends HookWidget {
return allAffectedDrugs;
}

Iterable<GenotypeResult> _getRelevantGenotypes(List<String>? drugSubset) {
return UserData.instance.genotypeResults == null
? []
: drugSubset != null
? UserData.instance.genotypeResults!.values.filter((genotypeResult) =>
_getAffectedDrugs(
genotypeResult.key.value,
drugSubset: drugSubset,
).isNotEmpty
)
: UserData.instance.genotypeResults!.values;
Iterable<GenotypeResult> _getRelevantGenotypes(
List<String>? drugSubset,
) {
if (UserData.instance.genotypeResults == null) return [];
final allGenotypeResults = UserData.instance.genotypeResults!.values;
if (drugSubset == null) return allGenotypeResults;
return allGenotypeResults.filter(
(genotypeResult) => _getAffectedDrugs(
genotypeResult.key.value,
drugSubset: drugSubset,
).isNotEmpty
);
}

List<Widget> _buildGeneCards({
required SortOption currentSortOption,
List<String>? drugsToFilterBy,
required String keyPostfix,
}) {
final userGenotypes = _getRelevantGenotypes(drugsToFilterBy);
final userGenotypes = _getRelevantGenotypes(
drugsToFilterBy,
);
final warningLevelCounts = <String, WarningLevelCounts>{};
for (final genotypeResult in userGenotypes) {
warningLevelCounts[genotypeResult.key.value] = {};
Expand Down Expand Up @@ -130,28 +112,29 @@ class ReportPage extends HookWidget {
GeneCard(
genotypeResult,
warningLevelCounts[genotypeResult.key.value]!,
key: Key('gene-card-${genotypeResult.key.value}'),
key: Key('gene-card-${genotypeResult.key.value}-$keyPostfix'),
useColors: false,
)
).toList();
}

Widget _maybeBuildPageIndicators(
BuildContext context,
Iterable<GenotypeResult> relevantGenes,
ActiveDrugs activeDrugs,
ListOption currentListOption,
{ required bool allGenesVisible }
) {
final drugsToFilterBy = allGenesVisible ? null : activeDrugs.names;
final relevantGenes = _getRelevantGenotypes(drugsToFilterBy);
final hasActiveInhibitors = relevantGenes.any(
(genotypeResult) => activeDrugs.names.any(
(drug) => isInhibited(genotypeResult, drug: drug)
)
);
if (!hasActiveInhibitors && currentListOption.drugSubset == null) {
if (!hasActiveInhibitors && drugsToFilterBy == null) {
return SizedBox.shrink();
}
var indicatorText = '';
if (currentListOption.drugSubset != null) {
if (drugsToFilterBy != null) {
final listHelperText = context.l10n.show_all_dropdown_text(
context.l10n.report_show_all_dropdown_item,
context.l10n.report_dropdown_position,
Expand All @@ -176,24 +159,9 @@ class ReportPage extends HookWidget {
Widget _buildReportPage(
BuildContext context,
ActiveDrugs activeDrugs,
ValueNotifier<int> currentListOptionIndex,
ValueNotifier<bool> allGenesExpanded,
ValueNotifier<SortOption> currentSortOption,
) {
final listOptions = onlyShowWholeReport
? [ListOption(label: context.l10n.report_all_medications)]
: [
ListOption(
label: context.l10n.report_current_medications,
drugSubset: activeDrugs.names,
),
ListOption(label: context.l10n.report_all_medications),
];
final currentListOption = listOptions[currentListOptionIndex.value];
final geneCards = _buildGeneCards(
currentSortOption: currentSortOption.value,
drugsToFilterBy: currentListOption.drugSubset,
);
final relevantGenes = _getRelevantGenotypes(currentListOption.drugSubset);
return PopScope(
canPop: false,
child: unscrollablePageScaffold(
Expand All @@ -204,60 +172,119 @@ class ReportPage extends HookWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PageDescription.fromText(context.l10n.report_content_explanation),
Padding(
key: Key('gene-report-selection'),
padding: EdgeInsets.only(
top: PharMeTheme.smallSpace,
left: PharMeTheme.smallSpace,
bottom: PharMeTheme.smallSpace,
right: PharMeTheme.mediumToLargeSpace,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
isExpanded: true,
isDense: false,
value: currentListOptionIndex.value,
onChanged: (index) => currentListOptionIndex.value =
index ?? currentListOptionIndex.value,
icon: Padding(
padding: EdgeInsets.only(left: PharMeTheme.smallSpace),
child: ResizedIconButton(
size: PharMeTheme.largeSpace,
disabledBackgroundColor: PharMeTheme.buttonColor,
iconWidgetBuilder: (size) => Icon(
Icons.arrow_drop_down,
size: size,
color: PharMeTheme.surfaceColor,
),
),
),
items: listOptions.mapIndexed(
(index, listOption) => DropdownMenuItem<int>(
value: index,
child: listOption.getDescription(
context,
_buildGeneCards(
drugsToFilterBy: listOption.drugSubset,
currentSortOption: currentSortOption.value,
).length
),
),
).toList(),
),
scrollList(
_buildReportLists(
context,
activeDrugs,
allGenesExpanded,
currentSortOption,
),
),
scrollList(geneCards),
_maybeBuildPageIndicators(
context,
relevantGenes,
activeDrugs,
currentListOption,
allGenesVisible: allGenesExpanded.value,
),
]
)
),
);
}

Widget _listDescription(
BuildContext context,
String label,
{ required List<String>? drugsToFilterBy }
) {
final genotypes = _getRelevantGenotypes(
drugsToFilterBy,
);
final affectedDrugs = genotypes.flatMap(
(genotypeResult) => _getAffectedDrugs(
genotypeResult.key.value,
drugSubset: drugsToFilterBy,
)
).toSet();
return Padding(
key: Key('list-description-$label'),
padding: EdgeInsets.symmetric(
vertical: PharMeTheme.mediumSpace,
horizontal: PharMeTheme.smallSpace,
),
child: Text.rich(
style: subheaderDividerStyle(),
TextSpan(
children: [
TextSpan(text: context.l10n.report_description_prefix),
TextSpan(text: ' '),
TextSpan(text: label, style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: ' '),
TextSpan(
text: '(${context.l10n.report_gene_number(genotypes.length)}, '
'${context.l10n.report_medication_number(affectedDrugs.length)})',
style: TextStyle(color: PharMeTheme.buttonColor),
),
],
),
),
);
}

List<Widget> _buildReportLists(
BuildContext context,
ActiveDrugs activeDrugs,
ValueNotifier<bool> allGenesExpanded,
ValueNotifier<SortOption> currentSortOption,
) {
final currentMedicationGenes = _buildGeneCards(
currentSortOption: currentSortOption.value,
drugsToFilterBy: activeDrugs.names,
keyPostfix: 'current-medications',
);
final allMedicationGenesHeader = _listDescription(
context,
context.l10n.report_all_medications,
drugsToFilterBy: null,
);
final allMedicationGenes = _buildGeneCards(
currentSortOption: currentSortOption.value,
drugsToFilterBy: null,
keyPostfix: 'all-medications',
);
if (currentMedicationGenes.isEmpty) {
return [
allMedicationGenesHeader,
...allMedicationGenes,
];
}
return [
_listDescription(
context,
context.l10n.report_current_medications,
drugsToFilterBy: activeDrugs.names,
),
...currentMedicationGenes,
PrettyExpansionTile(
title: allMedicationGenesHeader,
initiallyExpanded: allGenesExpanded.value,
onExpansionChanged: (value) => allGenesExpanded.value = value,
titlePadding: EdgeInsets.zero,
childrenPadding: EdgeInsets.zero,
icon: ResizedIconButton(
size: PharMeTheme.largeSpace,
disabledBackgroundColor: PharMeTheme.buttonColor,
iconWidgetBuilder: (size) => Icon(
allGenesExpanded.value
? Icons.arrow_drop_up
: Icons.arrow_drop_down,
size: size,
color: PharMeTheme.surfaceColor,
),
),
children: allMedicationGenes,
),
];
}
}

bool _hasNoResult(GenotypeResult genotypeResult) =>
Expand Down

0 comments on commit 6b24160

Please sign in to comment.