From b7568d55e89ca915311864a55390da0c585dbf35 Mon Sep 17 00:00:00 2001 From: Tamara Slosarek Date: Thu, 11 Apr 2024 16:06:17 +0200 Subject: [PATCH] feat(#259): add whole tutorial flow --- app/lib/common/models/drug/warning_level.dart | 54 ++++++++++++++++ app/lib/common/widgets/direction_button.dart | 11 +++- .../widgets/tutorial/show_app_tour.dart | 39 ++++++++++++ .../widgets/tutorial/tutorial_builder.dart | 15 +++-- app/lib/l10n/app_en.arb | 13 +++- app/lib/main/pages/main.dart | 2 +- app/lib/report/pages/report.dart | 63 +------------------ 7 files changed, 126 insertions(+), 71 deletions(-) diff --git a/app/lib/common/models/drug/warning_level.dart b/app/lib/common/models/drug/warning_level.dart index 9709bdaa..73b6f4c8 100644 --- a/app/lib/common/models/drug/warning_level.dart +++ b/app/lib/common/models/drug/warning_level.dart @@ -68,3 +68,57 @@ extension WarningLevelColor on WarningLevel { Color get color => WarningLevelColor._colorMap[name]!; Color get textColor => darkenColor(color, 0.4); } + +extension WarningLevelDescription on WarningLevel { + TextSpan getDescription(String text) => TextSpan( + children: [ + WidgetSpan( + child: Icon( + icon, + color: textColor, + size: PharMeTheme.textTheme.bodyMedium!.fontSize, + ), + ), + WidgetSpan( + child: SizedBox(width: PharMeTheme.smallSpace * 0.4), + ), + TextSpan( + text: text, + style: PharMeTheme.textTheme.bodyMedium!.copyWith(color: textColor) + ), + ], + ); +} + +extension WarningLevelLegend on List { + TextSpan buildLegend({ + required String? Function(WarningLevel) getText, + InlineSpan? separator, + }) { + var content = []; + for (final (index, warningLevel) in indexed) { + final text = getText(warningLevel); + if (text.isNullOrEmpty) continue; + final warningLevelIndicator = warningLevel.getDescription(text!); + final isLastItem = index == WarningLevel.values.length - 1; + content = isLastItem + ? [ ...content, warningLevelIndicator ] + : [ + ...content, + warningLevelIndicator, + separator ?? WidgetSpan( + child: SizedBox(width: PharMeTheme.smallSpace * 0.8), + ), + ]; + } + return TextSpan( + style: PharMeTheme.textTheme.bodyMedium, + children: content, + ); + } + + TextSpan getTextLegend(BuildContext context) => buildLegend( + getText: (warningLevel) => warningLevel.getLabel(context), + separator: TextSpan(text: ', '), + ); +} diff --git a/app/lib/common/widgets/direction_button.dart b/app/lib/common/widgets/direction_button.dart index 2ecda2d0..0344fe9f 100644 --- a/app/lib/common/widgets/direction_button.dart +++ b/app/lib/common/widgets/direction_button.dart @@ -41,9 +41,14 @@ class DirectionButton extends StatelessWidget { color: textColor, size: 32, ); - final buttonText = Text( - text, - style: PharMeTheme.textTheme.titleLarge!.copyWith(color: textColor), + final buttonText = Flexible( + child: Text( + text, + style: PharMeTheme.textTheme.titleLarge!.copyWith( + color: textColor, + overflow: TextOverflow.fade, + ), + ), ); final buttonContent = direction == ButtonDirection.forward ? [ separator, buttonText, separator, icon ] diff --git a/app/lib/common/widgets/tutorial/show_app_tour.dart b/app/lib/common/widgets/tutorial/show_app_tour.dart index dfbe22fa..5f7b388f 100644 --- a/app/lib/common/widgets/tutorial/show_app_tour.dart +++ b/app/lib/common/widgets/tutorial/show_app_tour.dart @@ -22,6 +22,45 @@ FutureOr showAppTour( assetPath: 'assets/images/tutorial/05_bottom_navigation_loopable.gif', ), + TutorialPage( + title: (context) => + context.l10n.tutorial_app_tour_2_title, + content: (context) => TextSpan( + children: [ + TextSpan(text: context.l10n.tutorial_app_tour_2_body), + WarningLevel.values.getTextLegend(context), + ], + ), + assetPath: + 'assets/images/tutorial/06_drug_search_and_filter_loopable.gif', + ), + TutorialPage( + title: (context) => + context.l10n.tutorial_app_tour_3_title, + content: (context) => TextSpan( + text: context.l10n.tutorial_app_tour_3_body, + ), + assetPath: + 'assets/images/tutorial/07_clopidogrel_loopable.gif', + ), + TutorialPage( + title: (context) => + context.l10n.tutorial_app_tour_4_title, + content: (context) => TextSpan( + text: context.l10n.tutorial_app_tour_4_body, + ), + assetPath: + 'assets/images/tutorial/08_report_and_cyp2c19_loopable.gif', + ), + TutorialPage( + title: (context) => + context.l10n.tutorial_app_tour_5_title, + content: (context) => TextSpan( + text: context.l10n.tutorial_app_tour_5_body, + ), + assetPath: + 'assets/images/tutorial/09_faq_and_more_loopable.gif', + ), ], onClose: revisiting ? null diff --git a/app/lib/common/widgets/tutorial/tutorial_builder.dart b/app/lib/common/widgets/tutorial/tutorial_builder.dart index 4e07b3f6..761bad3e 100644 --- a/app/lib/common/widgets/tutorial/tutorial_builder.dart +++ b/app/lib/common/widgets/tutorial/tutorial_builder.dart @@ -40,7 +40,10 @@ class TutorialBuilder extends HookWidget { ? currentPage.content!(context) : null; final asset = currentPage.assetPath != null - ? Image.asset(currentPage.assetPath!) + ? Container( + color: PharMeTheme.onSurfaceColor, + child: Center(child: Image.asset(currentPage.assetPath!)), + ) : null; return [ if (title != null) Text( @@ -57,7 +60,7 @@ class TutorialBuilder extends HookWidget { child: Padding( padding: EdgeInsetsDirectional.only(top: PharMeTheme.mediumSpace), // child: Container(), - child: Center(child: asset), + child: asset, ), ), Padding( @@ -74,7 +77,10 @@ class TutorialBuilder extends HookWidget { final isFirstPage = currentPageIndex.value == 0; final isLastPage = currentPageIndex.value == pages.length - 1; return Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: isFirstPage + ? MainAxisAlignment.end + : MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, children: [ if (!isFirstPage) DirectionButton( direction: ButtonDirection.backward, @@ -86,11 +92,10 @@ class TutorialBuilder extends HookWidget { onPressed: isLastPage ? Navigator.of(context).pop : () => currentPageIndex.value = currentPageIndex.value + 1, - text: isLastPage && lastNextButtonText != null ? lastNextButtonText! : context.l10n.action_continue, - emphasize: isLastPage, + emphasize: true, ), ], ); diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index c81a4b3b..6fbb6fa3 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -3,6 +3,7 @@ "action_cancel": "Cancel", "action_continue": "Continue", "action_back_to_app": "Back to app", + "action_finish": "Finish", "error_title": "Something went wrong", "error_uncaught_message_first_part": "PharMe has encountered an unknown error. ", @@ -347,11 +348,19 @@ "nav_more": "More", "tab_more": "More", - "tutorial_to_the_app": "Proceed to the app", "tutorial_initial_drug_selection_title": "Setup PharMe", "tutorial_initial_drug_selection_body": "As a first step, please update the list of your current medications.", "tutorial_app_tour_1_title": "App Tour (1/5) · Navigation", - "tutorial_app_tour_1_body": "We would first like to guide you through the app's main functions.\n\nYou can switch between PharMe's main screens using the bottom navigation bar.\n\nIf you want to re-watch this app tour later, you can always do so on the last screen under More.", + "tutorial_app_tour_1_body": "We would first like to guide you through the app's main functions.\n\nYou can switch between PharMe's main screens using the bottom navigation bar – if you want to re-watch this app tour later, you can always do so on the last screen under More.", + "tutorial_app_tour_2_title": "App Tour (2/5) · Medication List", + "tutorial_app_tour_2_body": "Under Medications, you will find the list of all available medications in PharMe.\n\nYou can search for specific generic names, brand names, or medication classes, and filter the list by various criteria.\n\nAll medications in PharMe are labeled with a color and an icon: ", + "tutorial_app_tour_3_title": "App Tour (3/5) · Medication Details", + "tutorial_app_tour_3_body": "The medication details provide further information about how well this medication works for you, according to scientific guidelines.\n\nHere you can also change whether you are currently taking a medication and export a report for healthcare professionals.", + "tutorial_app_tour_4_title": "App Tour (4/5) · Gene Report", + "tutorial_app_tour_4_body": "Under Genes, you will find the results of your genetic test for genes with known medication interactions.\n\nSelect a gene to learn more about your results and how this gene might interact with specific medications.", + "tutorial_app_tour_5_title": "App Tour (5/5) · FAQ & Additional Features", + "tutorial_app_tour_5_body": "Under FAQ, you will find a list of frequently asked questions and further resources.\n\nUnder More, you will find additional information about the app as well as a contact form and other useful features. Please reach out if you have any questions while using the app!", + "onboarding_get_started": "Get started", "onboarding_next": "Next", "onboarding_prev": "Back", diff --git a/app/lib/main/pages/main.dart b/app/lib/main/pages/main.dart index 38525574..f5bf77bf 100644 --- a/app/lib/main/pages/main.dart +++ b/app/lib/main/pages/main.dart @@ -49,7 +49,7 @@ class MainPage extends StatelessWidget { WidgetsBinding.instance.addPostFrameCallback((_) async { await showAppTour( context, - lastNextButtonText: context.l10n.tutorial_to_the_app, + lastNextButtonText: context.l10n.action_finish, revisiting: false, ); }); diff --git a/app/lib/report/pages/report.dart b/app/lib/report/pages/report.dart index 35d9d61e..e8a6547a 100644 --- a/app/lib/report/pages/report.dart +++ b/app/lib/report/pages/report.dart @@ -42,11 +42,7 @@ class ReportPage extends StatelessWidget { children: [ Text(context.l10n.report_legend_text), SizedBox(height: PharMeTheme.smallSpace * 0.5), - _buildWarningLevelIndicators( - getText: (warningLevel) => - warningLevel.getLabel(context), - separator: TextSpan(text: ', ') - ), + Text.rich(WarningLevel.values.getTextLegend(context)), ] ), ), @@ -110,7 +106,7 @@ class GeneCard extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - _buildWarningLevelIndicators( + Text.rich(WarningLevel.values.buildLegend( getText: (warningLevel) { final warningLevelCount = affectedDrugs.filter( (drug) => drug.warningLevel == warningLevel @@ -118,7 +114,7 @@ class GeneCard extends StatelessWidget { return warningLevelCount > 0 ? warningLevelCount.toString() : null; - }, + }), ), ], ), @@ -130,56 +126,3 @@ class GeneCard extends StatelessWidget { ); } } - -Text _buildWarningLevelIndicators({ - required String? Function(WarningLevel) getText, - InlineSpan? separator, -}) { - var content = []; - for (final (index, warningLevel) in WarningLevel.values.indexed) { - final text = getText(warningLevel); - if (text.isNullOrEmpty) continue; - final warningLevelIndicator = _buildWarningLevelIndicator( - warningLevel, - text: text!, - ); - final isLastItem = index == WarningLevel.values.length - 1; - content = isLastItem - ? [ ...content, ...warningLevelIndicator ] - : [ - ...content, - ...warningLevelIndicator, - separator ?? WidgetSpan( - child: SizedBox(width: PharMeTheme.smallSpace * 0.8), - ), - ]; - } - return Text.rich( - TextSpan( - style: PharMeTheme.textTheme.bodyMedium, - children: content, - ) - ); -} - -List _buildWarningLevelIndicator( - WarningLevel warningLevel, - { required String text } -) { - return [ - WidgetSpan( - child: Icon( - warningLevel.icon, - color: warningLevel.textColor, - size: PharMeTheme.textTheme.bodyMedium!.fontSize, - ), - ), - WidgetSpan( - child: SizedBox(width: PharMeTheme.smallSpace * 0.4), - ), - TextSpan( - text: text, - style: PharMeTheme.textTheme.bodyMedium!.copyWith(color: warningLevel.textColor) - ), - ]; -}