Skip to content

Commit

Permalink
feat(#259): add tutorial components and initial setup instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
tamslo committed Apr 5, 2024
1 parent 14c271a commit d36a2f9
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 48 deletions.
4 changes: 4 additions & 0 deletions app/lib/common/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class PharMeTheme {
surfaceVariant: surfaceColor,
),
textTheme: textTheme,
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: surfaceColor,
dragHandleColor: onSurfaceColor,
)
);
}

Expand Down
62 changes: 62 additions & 0 deletions app/lib/common/widgets/direction_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'dart:async';

import '../module.dart';

enum ButtonDirection { forward, backward }

class DirectionButton extends StatelessWidget {
const DirectionButton({
super.key,
required this.direction,
required this.text,
required this.onPressed,
this.emphasize = false,
this.onDarkBackground = false,
});

final ButtonDirection direction;
final String text;
final FutureOr<void> Function() onPressed;
final bool emphasize;
final bool onDarkBackground;

@override
Widget build(BuildContext context) {
const lightColor = Colors.white;
const darkColor = PharMeTheme.onSurfaceText;
final buttonStyle = emphasize
? ElevatedButton.styleFrom(
backgroundColor: onDarkBackground ? lightColor : darkColor,
)
: null;
final textColor = emphasize == onDarkBackground
? PharMeTheme.onSurfaceText
: Colors.white;
final separator = SizedBox(width: 8);
final iconData = direction == ButtonDirection.forward
? Icons.arrow_forward_rounded
: Icons.arrow_back_rounded;
final icon = Icon(
iconData,
color: textColor,
size: 32,
);
final buttonText = Text(
text,
style: PharMeTheme.textTheme.titleLarge!.copyWith(color: textColor),
);
final buttonContent = direction == ButtonDirection.forward
? [ separator, buttonText, separator, icon ]
: [ icon, separator, buttonText, separator ];
return TextButton(
style: buttonStyle,
onPressed: onPressed,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: buttonContent,
),
);
}

}
3 changes: 3 additions & 0 deletions app/lib/common/widgets/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export 'checkbox_wrapper.dart';
export 'dialog_action.dart';
export 'dialog_content_text.dart';
export 'dialog_wrapper.dart';
export 'direction_button.dart';
export 'drug_list/builder.dart';
export 'drug_list/cubit.dart';
export 'drug_list/drug_items/drug_cards.dart';
Expand All @@ -25,3 +26,5 @@ export 'radiant_gradient_mask.dart';
export 'rounded_card.dart';
export 'scroll_list.dart';
export 'subheader_divider.dart';
export 'tutorial/content.dart';
export 'tutorial/show.dart';
106 changes: 106 additions & 0 deletions app/lib/common/widgets/tutorial/container.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import 'dart:async';

import '../../module.dart';

class TutorialContainer extends HookWidget {
const TutorialContainer({
super.key,
required this.pages,
required this.finishTutorial,
this.lastNextButtonText,
});

final List<TutorialContent> pages;
final Future<void> Function() finishTutorial;
final String? lastNextButtonText;

@override
Widget build(BuildContext context) {
final currentPageIndex = useState(0);
return Padding(
padding: EdgeInsets.only(
left: PharMeTheme.largeSpace,
bottom: PharMeTheme.largeSpace,
right: PharMeTheme.largeSpace,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildPageContent(context, currentPageIndex),
),
);
}

List<Widget> _buildPageContent(
BuildContext context,
ValueNotifier<int> currentPageIndex,
) {
final currentPage = pages[currentPageIndex.value];
final title = currentPage.title != null
? currentPage.title!(context)
: null;
final content = currentPage.content != null
? currentPage.content!(context)
: null;
final asset = currentPage.assetPath != null
? Image.asset(currentPage.assetPath!)
: null;
return [
if (title != null) Text(
title,
style: PharMeTheme.textTheme.headlineMedium!.copyWith(
fontSize: PharMeTheme.textTheme.headlineSmall!.fontSize,
),
),
if (content != null || asset != null) Padding(
padding: EdgeInsetsDirectional.only(top: PharMeTheme.mediumSpace),
child: Expanded(
child: Center(child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
if (content != null)
Text.rich(content, style: PharMeTheme.textTheme.bodyLarge),
if (asset != null) asset,
],
),
)),
),
),
Padding(
padding: EdgeInsetsDirectional.only(top: PharMeTheme.mediumSpace),
child: _buildActionBar(context, currentPageIndex),
),
];
}

Widget _buildActionBar(
BuildContext context,
ValueNotifier<int> currentPageIndex,
) {
final isFirstPage = currentPageIndex.value == 0;
final isLastPage = currentPageIndex.value == pages.length - 1;
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (!isFirstPage) DirectionButton(
direction: ButtonDirection.backward,
onPressed: () => currentPageIndex.value = currentPageIndex.value - 1,
text: context.l10n.onboarding_prev,
),
DirectionButton(
direction: ButtonDirection.forward,
onPressed: isLastPage
? finishTutorial
: () => currentPageIndex.value = currentPageIndex.value + 1,

text: isLastPage && lastNextButtonText != null
? lastNextButtonText!
: context.l10n.action_continue,
emphasize: isLastPage,
),
],
);
}
}
13 changes: 13 additions & 0 deletions app/lib/common/widgets/tutorial/content.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import '../../module.dart';

class TutorialContent {
TutorialContent({
this.title,
this.content,
this.assetPath,
});

final String Function(BuildContext)? title;
final TextSpan Function(BuildContext)? content;
final String? assetPath;
}
30 changes: 30 additions & 0 deletions app/lib/common/widgets/tutorial/show.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import '../../module.dart';
import 'container.dart';

Future<void> showTutorial({
required BuildContext context,
required List<TutorialContent> pages,
required bool concludesWholeTutorial,
}) => showModalBottomSheet(
context: context,
enableDrag: true,
showDragHandle: true,
isDismissible: false,
isScrollControlled: true,
useSafeArea: true,
elevation: 0,
builder: (context) => TutorialContainer(
pages: pages,
lastNextButtonText: concludesWholeTutorial
? context.l10n.tutorial_to_the_app
: null,
finishTutorial: () async {
final closeTutorial = Navigator.of(context).pop;
if (concludesWholeTutorial) {
MetaData.instance.tutorialDone = true;
await MetaData.save();
}
closeTutorial();
},
),
);
17 changes: 17 additions & 0 deletions app/lib/drug_selection/pages/drug_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ class DrugSelectionPage extends HookWidget {
create: (context) => cubit ?? DrugSelectionCubit(activeDrugs),
child: BlocBuilder<DrugSelectionCubit, DrugSelectionState>(
builder: (context, state) {
if (concludesOnboarding) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await showTutorial(
context: context,
pages: [
TutorialContent(
title: (context) =>
context.l10n.tutorial_initial_drug_selection_title,
content: (context) => TextSpan(
text: context.l10n.tutorial_initial_drug_selection_body,
),
),
],
concludesWholeTutorial: false,
);
});
}
return unscrollablePageScaffold(
title: context.l10n.drug_selection_header,
body: Column(
Expand Down
4 changes: 4 additions & 0 deletions app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@
"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.",

"onboarding_get_started": "Get started",
"onboarding_next": "Next",
"onboarding_prev": "Back",
Expand Down
61 changes: 13 additions & 48 deletions app/lib/onboarding/pages/onboarding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,14 @@ class OnboardingPage extends HookWidget {
PageController pageController,
bool isLastPage,
) {
final buttonStyle = isLastPage
? ElevatedButton.styleFrom(backgroundColor: Colors.white)
: null;
final textColor = isLastPage
? PharMeTheme.onSurfaceText
: Colors.white;
return TextButton(
return DirectionButton(
key: Key('nextButton'),
style: buttonStyle,
direction: ButtonDirection.forward,
text: isLastPage
? isRevisiting
? context.l10n.action_back_to_app
: context.l10n.onboarding_get_started
: context.l10n.onboarding_next,
onPressed: () async {
if (isLastPage) {
if (isRevisiting) {
Expand All @@ -184,28 +183,8 @@ class OnboardingPage extends HookWidget {
);
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(width: 8),
Text(
isLastPage
? isRevisiting
? context.l10n.action_back_to_app
: context.l10n.onboarding_get_started
: context.l10n.onboarding_next,
style: PharMeTheme.textTheme.headlineSmall!
.copyWith(color: textColor),
),
SizedBox(width: 8),
Icon(
Icons.arrow_forward_rounded,
color: textColor,
size: 32,
),
],
),
onDarkBackground: true,
emphasize: isLastPage,
);
}

Expand All @@ -215,31 +194,17 @@ class OnboardingPage extends HookWidget {
bool isFirstPage,
) {
if (!isFirstPage) {
return TextButton(
return DirectionButton(
key: Key('prevButton'),
direction: ButtonDirection.backward,
onPressed: () {
pageController.previousPage(
duration: Duration(milliseconds: 500),
curve: Curves.ease,
);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.arrow_back_rounded,
color: Colors.white,
size: 32,
),
SizedBox(width: 8),
Text(
context.l10n.onboarding_prev,
style: PharMeTheme.textTheme.headlineSmall!
.copyWith(color: Colors.white),
),
],
),
text: context.l10n.onboarding_prev,
onDarkBackground: true,
);
} else {
return SizedBox.shrink();
Expand Down
2 changes: 2 additions & 0 deletions pharme.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"irinotecan",
"Keycloak",
"lookupkey",
"LTRB",
"MedlinePlus",
"Metabolizer",
"mocktail",
Expand All @@ -71,6 +72,7 @@
"simvastatin",
"tacrolimus",
"tramadol",
"unscrollable",
"voriconazole"
]
}
Expand Down

0 comments on commit d36a2f9

Please sign in to comment.