diff --git a/app/CONTRIBUTING.md b/app/CONTRIBUTING.md index 14ad861ce..0bd19e710 100644 --- a/app/CONTRIBUTING.md +++ b/app/CONTRIBUTING.md @@ -132,25 +132,30 @@ screen. A simulator with the app in its initial state (or not installed) needs to be running. +Optional: set the time and status bar items as described in +[screenshots for publishing](#screenshots-for-publishing). + +The `generate_screendocs/run.sh` script will create screencasts +and screenshots. + +Run the script with `bash generate_screendocs/run.sh`. + ### Screencasts -The `generate_screendocs/generate_screencast.sh` script will create screencast. -It uses Xcode to record the screencast and [`ffmpeg`](https://ffmpeg.org/) -to cut the `full.mov` to relevant subsets (needs to be installed). +The script uses uses Xcode to record the screencast and +[`ffmpeg`](https://ffmpeg.org/) to cut the `full.mov` to relevant subsets +(needs to be installed). To generate GIFs used in the Tutorial, [ImageMagick](https://imagemagick.org/index.php) is used (which also needs to be installed). -Run the script with `bash generate_screendocs/generate_screencast.sh`. - -## Screenshots +### Screenshots -To update the screenshots in `../docs/screenshots` +The script automatically updates the screenshots in `../docs/screenshots` (used in [📑 App screens](../docs/App-screens.md), [📑 User instructions](../docs/User-instructions.html), and the -[README](./README.md)), run the following command: -`bash generate_screendocs/generate_screenshots.sh`. +[README](./README.md)). If the error `The following MissingPluginException was thrown running a test: MissingPluginException(No implementation found for method captureScreenshot on diff --git a/app/README.md b/app/README.md index 0935616b9..fdb3235b6 100644 --- a/app/README.md +++ b/app/README.md @@ -18,7 +18,7 @@ application offers to export a detailed description. PGx report | Gene details | Medication search | Medication details | :-: | :-: | :-: | :-: | (a) | (b) | (c) | (d) | -![report_screen](../docs/screenshots/gene-report.png) | ![gene_screen](../docs/screenshots/cyp2d6.png) | ![search_screen](../docs/screenshots/drug-search.png) | ![drug_screen](../docs/screenshots/clopidogrel.png) | +![report_screen](../docs/screenshots/gene-report-all.png) | ![gene_screen](../docs/screenshots/cyp2c9.png) | ![search_screen](../docs/screenshots/drug-search.png) | ![drug_screen](../docs/screenshots/ibuprofen.png) | _Please note that these screenshots might not represent the latest app version._ diff --git a/app/assets/images/tutorial/05_bottom_navigation_loopable.gif b/app/assets/images/tutorial/05_bottom_navigation_loopable.gif index 9f5f258ae..c73e1b984 100644 Binary files a/app/assets/images/tutorial/05_bottom_navigation_loopable.gif and b/app/assets/images/tutorial/05_bottom_navigation_loopable.gif differ diff --git a/app/assets/images/tutorial/06_drug_search_and_filter_loopable.gif b/app/assets/images/tutorial/06_drug_search_and_filter_loopable.gif index 98e056be8..14a4c58e0 100644 Binary files a/app/assets/images/tutorial/06_drug_search_and_filter_loopable.gif and b/app/assets/images/tutorial/06_drug_search_and_filter_loopable.gif differ diff --git a/app/assets/images/tutorial/07_ibuprofen_loopable.gif b/app/assets/images/tutorial/07_ibuprofen_loopable.gif index 3b1cfe337..7a95615c3 100644 Binary files a/app/assets/images/tutorial/07_ibuprofen_loopable.gif and b/app/assets/images/tutorial/07_ibuprofen_loopable.gif differ diff --git a/app/assets/images/tutorial/08_report_and_cyp2c9_loopable.gif b/app/assets/images/tutorial/08_report_and_cyp2c9_loopable.gif index 6478ccb62..e1856b761 100644 Binary files a/app/assets/images/tutorial/08_report_and_cyp2c9_loopable.gif and b/app/assets/images/tutorial/08_report_and_cyp2c9_loopable.gif differ diff --git a/app/assets/images/tutorial/09_faq_and_more_loopable.gif b/app/assets/images/tutorial/09_faq_and_more_loopable.gif index 232e36b6c..e2f545fa4 100644 Binary files a/app/assets/images/tutorial/09_faq_and_more_loopable.gif and b/app/assets/images/tutorial/09_faq_and_more_loopable.gif differ diff --git a/app/generate_screendocs/generate_screenshots.sh b/app/generate_screendocs/generate_screenshots.sh deleted file mode 100644 index 07d3e3230..000000000 --- a/app/generate_screendocs/generate_screenshots.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -read -p "Enter username: " username -read -p "Enter password: " password - -flutter drive \ - --driver=generate_screendocs/test_driver.dart \ - --target=generate_screendocs/screenshot_sequence.dart \ - --dart-define=TEST_USER="$username" \ - --dart-define=TEST_PASSWORD="$password" diff --git a/app/generate_screendocs/generate_screencast.sh b/app/generate_screendocs/run.sh similarity index 98% rename from app/generate_screendocs/generate_screencast.sh rename to app/generate_screendocs/run.sh index 00bb32120..fb1ce0eb1 100644 --- a/app/generate_screendocs/generate_screencast.sh +++ b/app/generate_screendocs/run.sh @@ -37,10 +37,13 @@ if $redo_recording; then sleep 5 export RECORDING_PID=${!} echo "Recording process up with pid: ${RECORDING_PID}" + + # Run sequence in app + echo "Running app" flutter drive \ --driver=generate_screendocs/test_driver.dart \ - --target=generate_screendocs/screencast_sequence.dart \ + --target=generate_screendocs/screendocs_sequence.dart \ --dart-define=TIMESTAMP_PREFIX="$timestamp_prefix" \ --dart-define=TEST_USER="$username" \ --dart-define=TEST_PASSWORD="$password" | tee "$flutter_log_path" diff --git a/app/generate_screendocs/screencast_sequence.dart b/app/generate_screendocs/screencast_sequence.dart deleted file mode 100644 index b6fb43e59..000000000 --- a/app/generate_screendocs/screencast_sequence.dart +++ /dev/null @@ -1,66 +0,0 @@ -// Clicks though most parts of the app and outputs timestamp logs for cutting -// smaller screencasts in generate_screencast.sh script - -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import 'sequence_utils.dart'; - -void logTimeStamp(String prefix, String description) { - final timestamp = DateTime.now().millisecondsSinceEpoch; - // ignore: avoid_print - print('$prefix$timestamp $description'); -} - -Future beginPart( - WidgetTester tester, - String timestampPrefix, - String description, -) async{ - await settleAndWait(tester, 1); - logTimeStamp(timestampPrefix, description); - await settleAndWait(tester, 1); -} - -Future endPart( - WidgetTester tester, - String timestampPrefix, - String description, -) async { - await settleAndWait(tester, 1); - logTimeStamp(timestampPrefix, description); -} - -void main() { - group('click through the app and create screencasts', () { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('take screencast', (tester) async { - // ignore: unused_local_variable - const username = String.fromEnvironment('TEST_USER'); - // ignore: unused_local_variable - const password = String.fromEnvironment('TEST_PASSWORD'); - const timestampPrefix = String.fromEnvironment('TIMESTAMP_PREFIX'); - // ignore: unused_local_variable - const startOffset = int.fromEnvironment('START_OFFSET'); - - await loadApp(tester); - - // login - const loginDescription = 'login'; - await beginPart(tester, timestampPrefix, loginDescription); - await settleAndWait(tester, 3); - await endPart(tester, timestampPrefix, loginDescription); - - // login-redirect (not working; only taking screenshot of loading screen) - // could try to use cubit function to directly sign in which will only - // open the webview and close it again - // await tester.tap(find.byType(FullWidthButton).first); - // await Future.delayed(Duration(seconds: 3)); // wait for dialog - // await takeScreenshot(tester, binding, 'login-redirect'); - - // await beginPart(tester, timestampPrefix, moreDescription); - // await useBottomNavigation(tester, 'More'); - // await endPart(tester, timestampPrefix, moreDescription); - }); - }); -} \ No newline at end of file diff --git a/app/generate_screendocs/screendocs_sequence.dart b/app/generate_screendocs/screendocs_sequence.dart new file mode 100644 index 000000000..2552b1446 --- /dev/null +++ b/app/generate_screendocs/screendocs_sequence.dart @@ -0,0 +1,524 @@ +// Clicks though most parts of the app and creates screenshots (based on +// https://dev.to/mjablecnik/take-screenshot-during-flutter-integration-tests-435k) +// and screencasts; also outputs timestamp logs for cutting smaller screencasts + +import 'dart:io'; + +import 'package:app/app.dart'; +import 'package:app/common/module.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:provider/provider.dart'; + +Future takeScreenshot( + WidgetTester tester, + IntegrationTestWidgetsFlutterBinding binding, + String fileName +) async { + if (Platform.isAndroid) { + await binding.convertFlutterSurfaceToImage(); + await tester.pumpAndSettle(); + } + await binding.takeScreenshot(fileName); +} + +Future logTimeStamp( + WidgetTester tester, + String prefix, + String description, +) async { + await tester.pumpAndSettle(); + final timestamp = DateTime.now().millisecondsSinceEpoch; + // ignore: avoid_print + print('$prefix$timestamp $description'); + await _waitAndSettle(tester, 1); +} + +Future beginPart( + WidgetTester tester, + String timestampPrefix, + String description, +) async{ + await logTimeStamp(tester, timestampPrefix, description); +} + +Future endPart( + WidgetTester tester, + String timestampPrefix, + String description, +) async { + await logTimeStamp(tester, timestampPrefix, description); +} + +void main() { + group('click through the app and create screencasts', () { + final binding = IntegrationTestWidgetsFlutterBinding(); + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('take screencast', (tester) async { + const username = String.fromEnvironment('TEST_USER'); + const password = String.fromEnvironment('TEST_PASSWORD'); + const timestampPrefix = String.fromEnvironment('TIMESTAMP_PREFIX'); + + await _loadApp(tester); + + const acceptLoginDescription = '01_accept_and_login'; + const onboardingDescription = '02_onboarding'; + const drugSelectionDescription = '03_drug_selection'; + const tutorialDescription = '04_tutorial'; + const bottomNavigationDescription = '05_bottom_navigation_loopable'; + const drugSearchFilterDescription = '06_drug_search_and_filter_loopable'; + const ibuprofenDescription = '07_ibuprofen_loopable'; + const reportCyp2c9Description = '08_report_and_cyp2c9_loopable'; + const faqMoreDescription = '09_faq_and_more_loopable'; + const deleteDataDescription = '10_delete_data'; + + await beginPart( + tester, + timestampPrefix, + acceptLoginDescription, + ); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'accept-terms'); + await _tapButton(tester, 'Continue'); + await _waitAndSettle(tester, 2); + await _enterUsername(tester, username); + await _waitAndSettle(tester, 1); + await _enterPassword(tester, password); + await tester.pumpAndSettle(); + await takeScreenshot(tester, binding, 'login'); + await _tapFullWidthButton(tester); + await _waitAndSettle(tester, 1); + await endPart(tester, timestampPrefix, acceptLoginDescription); + + await beginPart( + tester, + timestampPrefix, + onboardingDescription, + ); + const onboardingWaitingTime = 1; + await _waitAndSettle(tester, onboardingWaitingTime); + await takeScreenshot(tester, binding, 'onboarding-1'); + await _tapButton(tester, 'Next'); + await _waitAndSettle(tester, onboardingWaitingTime); + await takeScreenshot(tester, binding, 'onboarding-2'); + await _tapButton(tester, 'Next'); + await _waitAndSettle(tester, onboardingWaitingTime); + await takeScreenshot(tester, binding, 'onboarding-3'); + await _tapButton(tester, 'Next'); + await _waitAndSettle(tester, onboardingWaitingTime); + await takeScreenshot(tester, binding, 'onboarding-4'); + await _tapButton(tester, 'Next'); + await _waitAndSettle(tester, onboardingWaitingTime); + await takeScreenshot(tester, binding, 'onboarding-5'); + await _tapButton(tester, 'Get started'); + await _waitAndSettle(tester, 1); + await endPart(tester, timestampPrefix, onboardingDescription); + + await beginPart(tester, timestampPrefix, drugSelectionDescription); + await _waitAndSettle(tester, 2); + await takeScreenshot(tester, binding, 'drug-selection-intro'); + await _tapButton(tester, 'Continue'); + await _waitAndSettle(tester, 1); + await _searchDrug(tester, 'Ibu'); + await _waitAndSettle(tester, 1); + await _interactWithDrugInSelection( + tester, + 'Ibuprofen', + scroll: false, + tap: true, + ); + await _waitAndSettle(tester, 1); + await _clearDrugSearch(tester); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'drug-selection'); + await _tapFullWidthButton(tester); + await _waitAndSettle(tester, 1); + await endPart(tester, timestampPrefix, drugSelectionDescription); + + await beginPart(tester, timestampPrefix, tutorialDescription); + const tutorialWaitingTime = 1; + await Future.delayed(Duration(seconds: tutorialWaitingTime)); + await takeScreenshot(tester, binding, 'tutorial-1'); + await _tapButton(tester, 'Continue'); + await _waitAndSettle(tester, tutorialWaitingTime); + await takeScreenshot(tester, binding, 'tutorial-2'); + await _tapButton(tester, 'Continue'); + await _waitAndSettle(tester, tutorialWaitingTime); + await takeScreenshot(tester, binding, 'tutorial-3'); + await _tapButton(tester, 'Continue'); + await _waitAndSettle(tester, tutorialWaitingTime); + await takeScreenshot(tester, binding, 'tutorial-4'); + await _tapButton(tester, 'Continue'); + await _waitAndSettle(tester, tutorialWaitingTime); + await takeScreenshot(tester, binding, 'tutorial-5'); + await _tapButton(tester, 'Finish'); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'setup-complete'); + await _selectDialogAction(tester); + await _waitAndSettle(tester, 1); + await endPart(tester, timestampPrefix, tutorialDescription); + + await beginPart(tester, timestampPrefix, bottomNavigationDescription); + await _waitAndSettle(tester, 1); + await _useBottomNavigation(tester, 'Genes'); + await _waitAndSettle(tester, 1); + await _useBottomNavigation(tester, 'FAQ'); + await _waitAndSettle(tester, 1); + await _useBottomNavigation(tester, 'More'); + await _waitAndSettle(tester, 1); + await _useBottomNavigation(tester, 'Medications'); + await _waitAndSettle(tester, 0); + await endPart(tester, timestampPrefix, bottomNavigationDescription); + + const toggledWarningLevelIndex = 3; // missing data + await beginPart(tester, timestampPrefix, drugSearchFilterDescription); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'drug-search'); + await _openDrugFilters(tester); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'drug-search-filter'); + await _toggleNthWarningLevel(tester, toggledWarningLevelIndex); + await _waitAndSettle(tester, 2); + await _toggleNthWarningLevel(tester, toggledWarningLevelIndex); + await _waitAndSettle(tester, 1); + await _closeDrugFilters(tester); + await _waitAndSettle(tester, 0); + await endPart(tester, timestampPrefix, drugSearchFilterDescription); + + await beginPart(tester, timestampPrefix, ibuprofenDescription); + await _waitAndSettle(tester, 1); + await _tapText(tester, 'Ibuprofen'); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'ibuprofen'); + await _changeDrugStatus(tester, 'Ibuprofen', 'inactive'); + await _waitAndSettle(tester, 2); + try { + await _changeDrugStatus(tester, 'Ibuprofen', 'active'); + await _waitAndSettle(tester, 2); + } catch (e) { + // ignore: avoid_print + print( + '🚨 Could not activate Ibuprofen again; video will not be' + 'loopable' + ); + } + await tester.pageBack(); + await _waitAndSettle(tester, 1); + await endPart(tester, timestampPrefix, ibuprofenDescription); + + await _waitAndSettle(tester, 1); + await _useBottomNavigation(tester, 'Genes'); + await _waitAndSettle(tester, 2); + + await beginPart(tester, timestampPrefix, reportCyp2c9Description); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'gene-report-current'); + await _toggleNonCurrentList(tester); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'gene-report-all'); + await _toggleNonCurrentList(tester, expectExpanded: true); + await _waitAndSettle(tester, 1); + await _tapGeneCard(tester, 'CYP2C9'); + await _waitAndSettle(tester, 2); + await takeScreenshot(tester, binding, 'cyp2c9'); + await _toggleNonCurrentList(tester); + await _waitAndSettle(tester, 2); + await takeScreenshot(tester, binding, 'cyp2c9-expanded'); + await _toggleNonCurrentList(tester, expectExpanded: true); + await _waitAndSettle(tester, 0); + await tester.pageBack(); + await _waitAndSettle(tester, 1); + await endPart(tester, timestampPrefix, reportCyp2c9Description); + + await _waitAndSettle(tester, 1); + await _useBottomNavigation(tester, 'FAQ'); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'faq'); + + await beginPart(tester, timestampPrefix, faqMoreDescription); + await _waitAndSettle(tester, 1); + await _tapFirstFaqItem(tester); + await _waitAndSettle(tester, 1); + await takeScreenshot(tester, binding, 'faq-first-item'); + await _tapFirstFaqItem(tester); + await _waitAndSettle(tester, 1); + await _useBottomNavigation(tester, 'More'); + await _waitAndSettle(tester, 3); + await takeScreenshot(tester, binding, 'more'); + await _tapText(tester, 'Contact us', selectLast: true); + await _waitAndSettle(tester, 2); + await _selectDialogAction(tester, selectFirst: true); + await _waitAndSettle(tester, 0); + await _useBottomNavigation(tester, 'FAQ'); + await _waitAndSettle(tester, 1); + await endPart(tester, timestampPrefix, faqMoreDescription); + + + await beginPart(tester, timestampPrefix, deleteDataDescription); + await _useBottomNavigation(tester, 'More'); + await tester.pumpAndSettle(); + await _tapText(tester, 'Delete app data'); + await tester.pumpAndSettle(); + await takeScreenshot(tester, binding, 'delete-app-data'); + await _selectDialogAction(tester, selectFirst: true); + await tester.pumpAndSettle(); + await endPart(tester, timestampPrefix, deleteDataDescription); + + await _cleanupApp(); + }); + }); +} + +Future _loadApp(WidgetTester tester) async { + // Part before runApp in lib/main.dart + await initServices(); + // Load the app + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => ActiveDrugs()), + ChangeNotifierProvider(create: (context) => InactivityTimer()), + ], + child: PharMeApp(), + ), + ); + await tester.pumpAndSettle(); +} + +Future _cleanupApp() async { + // Part after runApp in lib/main.dart + await cleanupServices(); +} + +Future _waitAndSettle(WidgetTester tester, int seconds) async { + await tester.pumpAndSettle(); + await Future.delayed(Duration(seconds: seconds)); + await tester.pumpAndSettle(); +} + +Future _enterText(WidgetTester tester, Finder finder, String text) async { + // Please note: apparently, this does not actually open the keyboard in the + // simulator (which is fine I guess) + await tester.showKeyboard(finder); + await tester.pump(); + var buildingSearchTerm = ''; + for (final character in text.characters) { + // ignore: use_string_buffers + buildingSearchTerm = '$buildingSearchTerm$character'; + await tester.enterText(finder, buildingSearchTerm); + await tester.pump(); + } + await _finishTextInput(tester); +} + +Future _enterUsername(WidgetTester tester, String username) async { + await _enterText(tester, find.byType(TextField).first, username); +} + +Future _enterPassword(WidgetTester tester, String password) async { + await _enterText(tester, find.byType(TextField).last, password); +} + +Future _searchDrug(WidgetTester tester, String drug) async { + await _enterText(tester, find.byType(CupertinoSearchTextField).last, drug); +} + +Future _clearDrugSearch(WidgetTester tester) async { + await tester.tap(find.descendant( + of: find.byType(CupertinoSearchTextField).first, + matching: find.byType(Icon), + ).last); +} + +Future _finishTextInput(WidgetTester tester) async { + await tester.testTextInput.receiveAction(TextInputAction.done); +} + +Future _tapButton(WidgetTester tester, String label) async { + await tester.tap(find.bySemanticsLabel(label).first); +} + +Future _toggleNonCurrentList( + WidgetTester tester, + { bool expectExpanded = false } +) async { + final expectedIcon = expectExpanded + ? Icons.arrow_drop_up + : Icons.arrow_drop_down; + await tester.tap( + find.ancestor( + of: find.byIcon(expectedIcon, skipOffstage: false), + matching: find.byType(ResizedIconButton, skipOffstage: false), + ).last, + ); +} + +Future _tapFullWidthButton(WidgetTester tester) async { + await tester.tap(find.byType(FullWidthButton).first); +} + +Finder _findText(String text, {bool selectLast = false}) { + final exactMatch = find.text(text, skipOffstage: false); + if (exactMatch.hasFound) { + return selectLast + ? exactMatch.last + : exactMatch.first; + } + final fuzzyMatch = find.textContaining(text, skipOffstage: false); + return selectLast ? fuzzyMatch.last : fuzzyMatch.first; +} + +Future _tapText( + WidgetTester tester, + String text, + {bool selectLast = false} +) async { + await tester.tap(_findText(text, selectLast: selectLast)); +} + +Future _tapGeneCard( + WidgetTester tester, + String gene, + { String keyPostfix = 'current-medications' } +) async { + final geneCard = find.byKey( + Key('gene-card-$gene-$keyPostfix'), + skipOffstage: false, + ); + await tester.tap(geneCard); +} + +// This is a bit hacky, as only some list items can be found currently +// (not sure why); therefore, the script needs to select one of those. +// If the interaction could not be executed, a list of available items is +// printed. +Future _interactWithDrugListItem( + WidgetTester tester, + { + required String itemKey, + required String listKey, + required Type itemType, + required bool scroll, + required bool tap, + required bool raiseException, + } +) async { + final listFinder = find.byKey(Key(listKey), skipOffstage: false); + final itemFinder = find.descendant( + of: listFinder, + matching: find.byKey(Key(itemKey), skipOffstage: false), + skipOffstage: false, + ); + final item = itemFinder.first; + try { + if (scroll) { + final list = listFinder.first; + await tester.dragUntilVisible(item, list, Offset(0, -0.1)); + } + if (tap) { + await tester.tap(item); + } + } catch (e) { + var errorMessage = '🚨 Could not drag drug list'; + if (!raiseException) errorMessage += '\n Error: ${e.toString()}\n'; + // ignore: avoid_print + print(errorMessage); + var contextDetails = 'Context details:\n' + ' With item finder: ${itemFinder.toString()}'; + if (scroll) { + contextDetails += '\n With list finder: ${listFinder.toString()}\n'; + } + // ignore: avoid_print + print(contextDetails); + // ignore: avoid_print + print('Available drug items:'); + final availableItems = find.descendant( + of: listFinder, + matching: find.byType(itemType, skipOffstage: false), + skipOffstage: false, + ); + availableItems.evaluate(); + // ignore: avoid_function_literals_in_foreach_calls + availableItems.found.forEach( + // ignore: avoid_print + (element) => print(' ${element.toString()}') + ); + if (raiseException) rethrow; + } +} + +Future _interactWithDrugInSelection( + WidgetTester tester, + String drug, + { + required bool scroll, + required bool tap, + bool raiseException = true, + } +) async { + await _interactWithDrugListItem( + tester, + itemKey: 'other-drug-selection-tile-${drug.toLowerCase()}', + listKey: 'drug-selection', + itemType: SwitchListTile, + scroll: scroll, + tap: tap, + raiseException: raiseException, + ); +} + +Future _useBottomNavigation( + WidgetTester tester, + String destination, +) async { + await tester.tap(find.descendant( + of: find.byType(BottomNavigationBar), + matching: find.text(destination), + ).first); +} + +Future _changeDrugStatus( + WidgetTester tester, + String drug, + String activity, +) async { + await tester.tap( + find.byType(Switch).first, + ); +} + +Future _tapFirstFaqItem(WidgetTester tester) async { + await tester.tap( + find.descendant( + of: find.byType(ExpansionTile).first, + matching: find.byType(Icon), + ), + ); +} + +Future _toggleNthWarningLevel(WidgetTester tester, int n) async { + // We are basically testing :shrug: + // ignore: invalid_use_of_visible_for_testing_member + await tester.tap(find.byType(WarningLevelFilterChip).at(n)); +} + +Future _selectDialogAction( + WidgetTester tester, + {bool selectFirst = false} +) async { + final actions = find.byType(DialogAction, skipOffstage: false); + final action = selectFirst ? actions.first : actions.last; + await tester.tap(action); +} + +Future _openDrugFilters(WidgetTester tester) async { + await tester.tap(find.ancestor( + of: find.byIcon(Icons.filter_list), + matching: find.byType(IconButton), + )); +} + +Future _closeDrugFilters(WidgetTester tester) async { + await tester.tapAt(Offset(0, 0)); +} diff --git a/app/generate_screendocs/screenshot_sequence.dart b/app/generate_screendocs/screenshot_sequence.dart deleted file mode 100644 index 5623394fc..000000000 --- a/app/generate_screendocs/screenshot_sequence.dart +++ /dev/null @@ -1,49 +0,0 @@ -// Clicks though most parts of the app and creates screenshots; based on -// https://dev.to/mjablecnik/take-screenshot-during-flutter-integration-tests-435k - -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import 'sequence_utils.dart'; - -Future takeScreenshot( - WidgetTester tester, - IntegrationTestWidgetsFlutterBinding binding, - String fileName -) async { - if (Platform.isAndroid) { - await binding.convertFlutterSurfaceToImage(); - await tester.pumpAndSettle(); - } - await binding.takeScreenshot(fileName); -} - -void main() { - group('click through the app and create screenshots', () { - final binding = IntegrationTestWidgetsFlutterBinding(); - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('take screenshots', (tester) async { - // ignore: unused_local_variable - const username = String.fromEnvironment('TEST_USER'); - // ignore: unused_local_variable - const password = String.fromEnvironment('TEST_PASSWORD'); - - await loadApp(tester); - - // login - await settleAndWait(tester, 5); // wait for logo - await takeScreenshot(tester, binding, 'login'); - - // login-redirect (not working; only taking screenshot of loading screen) - // could try to use cubit function to directly sign in which will only - // open the webview and close it again - // await tester.tap(find.byType(FullWidthButton).first); - // await Future.delayed(Duration(seconds: 3)); // wait for dialog - // await takeScreenshot(tester, binding, 'login-redirect'); - - await cleanupApp(); - }); - }); -} \ No newline at end of file diff --git a/app/generate_screendocs/sequence_utils.dart b/app/generate_screendocs/sequence_utils.dart deleted file mode 100644 index 86babba45..000000000 --- a/app/generate_screendocs/sequence_utils.dart +++ /dev/null @@ -1,252 +0,0 @@ -import 'package:app/app.dart'; -import 'package:app/common/module.dart'; -import 'package:app/drug/widgets/module.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:provider/provider.dart'; - -Future loadApp(WidgetTester tester) async { - // Part before runApp in lib/main.dart - await initServices(); - await maybeUpdateGenotypeResults(); - // Load the app - await tester.pumpWidget( - ChangeNotifierProvider( - create: (context) => ActiveDrugs(), - child: PharMeApp(), - ), - ); - await tester.pumpAndSettle(); -} - -Future cleanupApp() async { - // Part after runApp in lib/main.dart - await cleanupServices(); -} - -Future settleAndWait(WidgetTester tester, int seconds) async { - await tester.pumpAndSettle(); - await Future.delayed(Duration(seconds: seconds)); -} - -Future _enterText(WidgetTester tester, Finder finder, String text) async { - var buildingSearchTerm = ''; - for (final character in text.characters) { - // ignore: use_string_buffers - buildingSearchTerm = '$buildingSearchTerm$character'; - await tester.enterText(finder, buildingSearchTerm); - } -} - -Future _clearText(WidgetTester tester, Finder finder, String text) async { - var unbuildingSearchTerm = text; - for (final character in text.characters.reversed) { - // ignore: use_string_buffers - unbuildingSearchTerm = unbuildingSearchTerm.removeSuffix(character); - await tester.enterText(finder, unbuildingSearchTerm); - } -} - -Future enterUsername(WidgetTester tester, String username) async { - await _enterText(tester, find.byType(TextField).first, username); -} - -Future enterPassword(WidgetTester tester, String password) async { - await _enterText(tester, find.byType(TextField).last, password); -} - -Future searchDrug(WidgetTester tester, String drug) async { - await _enterText(tester, find.byType(CupertinoSearchTextField).last, drug); -} - -Future clearDrugSearch(WidgetTester tester, String drug) async { - await _clearText(tester, find.byType(CupertinoSearchTextField).last, drug); -} - -Future finishTextInput(WidgetTester tester) async { - await tester.testTextInput.receiveAction(TextInputAction.done); -} - -Future tapButton(WidgetTester tester, String label) async { - await tester.tap(find.bySemanticsLabel(label).first); -} - -Future tapFullWidthButton(WidgetTester tester) async { - await tester.tap(find.byType(FullWidthButton).first); -} - -Finder _findText(String text) { - final exactMatch = find.text(text, skipOffstage: false).first; - if (exactMatch.hasFound) return exactMatch; - return find.textContaining(text, skipOffstage: false).first; -} - -Future tapText(WidgetTester tester, String text) async { - await tester.tap(_findText(text)); -} - -Future jumpToText(WidgetTester tester, String text) async { - final target = find.text(text, skipOffstage: false).first; - await tester.ensureVisible(target); -} - -// This is a bit hacky, as only some list items can be found currently -// (not sure why); therefore, the script needs to select one of those. -// If the interaction could not be executed, a list of available items is -// printed. -Future _interactWithDrugListItem( - WidgetTester tester, - { - required String itemKey, - required String listKey, - required Type itemType, - required bool scroll, - required bool tap, - required bool raiseException, - } -) async { - final listFinder = find.byKey(Key(listKey), skipOffstage: false); - final itemFinder = find.descendant( - of: listFinder, - matching: find.byKey(Key(itemKey), skipOffstage: false), - skipOffstage: false, - ); - final item = itemFinder.first; - try { - if (scroll) { - final list = listFinder.first; - await tester.dragUntilVisible(item, list, Offset(0, -0.1)); - } - if (tap) { - await tester.tap(item); - } - } catch (e) { - var errorMessage = '🚨 Could not drag drug list'; - if (!raiseException) errorMessage += '\n Error: ${e.toString()}\n'; - // ignore: avoid_print - print(errorMessage); - var contextDetails = 'Context details:\n' - ' With item finder: ${itemFinder.toString()}'; - if (scroll) { - contextDetails += '\n With list finder: ${listFinder.toString()}\n'; - } - // ignore: avoid_print - print(contextDetails); - // ignore: avoid_print - print('Available drug items:'); - final availableItems = find.descendant( - of: listFinder, - matching: find.byType(itemType, skipOffstage: false), - skipOffstage: false, - ); - availableItems.evaluate(); - // ignore: avoid_function_literals_in_foreach_calls - availableItems.found.forEach( - // ignore: avoid_print - (element) => print(' ${element.toString()}') - ); - if (raiseException) rethrow; - } -} - -Future interactWithDrugInSelection( - WidgetTester tester, - String drug, - { - required bool scroll, - required bool tap, - bool raiseException = true, - } -) async { - await _interactWithDrugListItem( - tester, - itemKey: 'other-drug-selection-tile-${drug.toLowerCase()}', - listKey: 'drug-selection', - itemType: SwitchListTile, - scroll: scroll, - tap: tap, - raiseException: raiseException, - ); -} - -Future interactWithDrugInSearch( - WidgetTester tester, - String drug, - { - required bool scroll, - required bool tap, - bool raiseException = true, - } -) async { - await _interactWithDrugListItem( - tester, - itemKey: 'drug-card-${drug.toLowerCase()}', - listKey: 'drug-search', - itemType: DrugCard, - scroll: scroll, - tap: tap, - raiseException: raiseException, - ); -} - -Future useBottomNavigation( - WidgetTester tester, - String destination, -) async { - await tester.tap(find.descendant( - of: find.byType(BottomNavigationBar), - matching: find.text(destination), - ).first); -} - -Future changeDrugStatus( - WidgetTester tester, - String drug, - String activity, -) async { - await tester.tap( - find.byType(Switch).first, - ); -} - -Future tapFirstFaqItem(WidgetTester tester) async { - await tester.tap( - find.descendant( - of: find.byType(ExpansionTile).first, - matching: find.byType(Icon), - ), - ); -} - -Future tapDrugSearchTooltip(WidgetTester tester) async { - await tester.tap(find.descendant( - of: find.byType(DrugSearch), - matching: find.byType(TooltipIcon), - ).first); -} - -Future toggleNthWarningLevel(WidgetTester tester, int n) async { - // We are basically testing :shrug: - // ignore: invalid_use_of_visible_for_testing_member - await tester.tap(find.byType(WarningLevelFilterChip).at(n)); -} - -Future selectDialogAction(WidgetTester tester) async { - await tester.tap(find.byType(DialogAction)); -} - -Future openDrugFilters(WidgetTester tester) async { - await tester.tap(find.ancestor( - of: find.byIcon(Icons.filter_list), - matching: find.byType(IconButton), - )); -} - -Future closeDrugFilters(WidgetTester tester) async { - await tester.tapAt(Offset(0, 0)); -} - -// Sometimes tester.pageBack is not working, then can use this -Future tapBackButton(WidgetTester tester) async { - await tester.tap(find.byType(IconButton).first,warnIfMissed: false); -} diff --git a/docs/App-screens.md b/docs/App-screens.md index 29d3f4fe9..711b17f71 100644 --- a/docs/App-screens.md +++ b/docs/App-screens.md @@ -1,8 +1,5 @@ # App Screens -_The last export date is September 08, 2023. Changes applied afterwards are not_ -_depicted._ - @@ -13,28 +10,20 @@ table row. | # | Action | Screen | Description | | - | ------ | ------ | ----------- | -| 1 | App opened the first time | login | Login screen with data provider selection | -| 2 | _Get data_ 👆 | login-redirect | Alert for login redirect | -| 3 | _Continue_ 👆 | keycloak-login | Redirect to Keycloak login page | -| 4 | _Sign In_ 👆 | import-success | Back to app, import was successful | -| 5 | _Continue_ 👆 | onboarding-1 | Onboarding (screen 1 of 5) | -| 6 | _Next_ 👆 | onboarding-2 | Onboarding (screen 2 of 5) | -| 7 | _Next_ 👆 | onboarding-3 | Onboarding (screen 3 of 5) | -| 8 | _Next_ 👆 | onboarding-4 | Onboarding (screen 4 of 5) | -| 9 | _Next_ 👆 | onboarding-5 | Onboarding (screen 5 of 5) | -| 10 | _Get started_ 👆 | drug-selection | Initial selection of current medications | -| 11 | _Continue_ 👆 | gene-report | Gene report, showing all genes that can be mapped to PGx guidelines | -| 12 | _CYP2D6_ tile 👆 | cyp2d6 | Gene details; the notice about influence of other medications is only shown for genes for which phenoconversion implemented | -| 13 | _Amitriptyline_ tile 👆 | amitriptyline | Medication with unknown guideline detail; shown in green since standard dosing is applied without guidelines (_note for this example: the guideline for this genotype was not published in the backend at time of screenshot creation, which is why the guideline in missing in this case_) | -| 14 | _Medications_ navigation tab 👆 | drug-search | Medication search page | -| 15 | _?_ icon 👆 | drug-search-tooltip | Tooltip explaining search feature; tooltips look the same on all pages | -| 16 | Filter icon 👆 | drug-search-filter | Available search filters | -| 17 | _Clopidogrel_ tile 👆 | clopidogrel | Medication with known guideline | -| 18 | ⏬ | clopidogrel-scrolled | At the bottom of a medication, a link to the underlying guideline is given; this link redirects the user to the guideline website | -| 19 | Share icon (in header) 👆 | pdf-export | Create a PDF document to share with others | -| 20 | _FAQ_ navigation tab 👆 | faq | FAQ page | -| 21 | First FAQ list item 👆 | faq-first-item | Extended FAQ item | -| 22 | ⏬ | faq-contact | "Contact us" at the end of the FAQ in case of more questions; will open the user's default email app with the development team address pre-filled | -| 23 | _More_ navigation tab 👆 | more | "More" page with settings and further information; "Onboarding" will start the onboarding again (screens 5 to 10) | -| 24 | #23 _About us_ 👆 | about-us | "About us" page; "Privacy policy" and "Terms of use" have the same page style (currently only lorem ipsum) | -| 25 | #23 _Delete app data_ 👆 | delete-app-data | Deletes all app data and redirects to screen 1; continuing is only possible when the checkmark was clicked | +| 1 | App opened the first time | accept-terms | Notice that by continuing users agree to terms (see screen 23) | +| 2 | _Continue_ 👆 | login | Login screen with "Contact us" link; will open the user's default email app with the study team address pre-filled | +| 3 | #2 _Sign in_ (correct credentials) 👆 | onboarding-1 | Onboarding (screen 1 of 5) | +| 4 | _Next_ 👆 | onboarding-2 | Onboarding (screen 2 of 5) | +| 5 | _Next_ 👆 | onboarding-3 | Onboarding (screen 3 of 5) | +| 6 | _Next_ 👆 | onboarding-4 | Onboarding (screen 4 of 5) | +| 7 | _Next_ 👆 | onboarding-5 | Onboarding (screen 5 of 5) | +| 8 | _Get started_ 👆 | drug-selection | Initial active medication selection | +| 9 | _Continue_ 👆 | gene-report | Gene report, showing all genes that can be mapped to PGx guidelines | +| 10 | #10 _CYP2C9_ tile 👆 | cyp2c9 | Gene details; a notice about influence of other medications is shown for genes where drug-gene interactions are implemented | +| 11 | _Medications_ navigation tab 👆 | drug-search | Medication search page | +| 12 | Filter icon 👆 | drug-search-filter | Available search filters | +| 13 | _Ibuprofen_ tile 👆 | ibuprofen | Medication with "yellow" warning level guideline | +| 14 | _FAQ_ navigation tab 👆 | faq | FAQ page | +| 15 | First FAQ list item 👆 | faq-first-item | Extended FAQ item; the last item is a "Contact us" button (not visible in screenshot) in case of more questions | +| 16 | _More_ navigation tab 👆 | more | "More" page with settings and further information; "Onboarding" will start the onboarding again (screens 5 to 10) | +| 17 | #16 _Delete app data_ 👆 | delete-app-data | Deletes all app data and redirects to screen 1; continuing is only possible when the checkmark was clicked | diff --git a/docs/User-instructions.html b/docs/User-instructions.html index c28111200..c35bae01f 100644 --- a/docs/User-instructions.html +++ b/docs/User-instructions.html @@ -99,8 +99,8 @@

Getting Started with PharMe

-
-
+
+
diff --git a/docs/screencasts/01_accept_and_login.gif b/docs/screencasts/01_accept_and_login.gif new file mode 100644 index 000000000..740f9bffc Binary files /dev/null and b/docs/screencasts/01_accept_and_login.gif differ diff --git a/docs/screencasts/01_accept_and_login.mp4 b/docs/screencasts/01_accept_and_login.mp4 new file mode 100644 index 000000000..acebc5fac Binary files /dev/null and b/docs/screencasts/01_accept_and_login.mp4 differ diff --git a/docs/screencasts/02_onboarding.gif b/docs/screencasts/02_onboarding.gif new file mode 100644 index 000000000..ca344e0ae Binary files /dev/null and b/docs/screencasts/02_onboarding.gif differ diff --git a/docs/screencasts/02_onboarding.mp4 b/docs/screencasts/02_onboarding.mp4 new file mode 100644 index 000000000..87da0bc06 Binary files /dev/null and b/docs/screencasts/02_onboarding.mp4 differ diff --git a/docs/screencasts/03_drug_selection.gif b/docs/screencasts/03_drug_selection.gif new file mode 100644 index 000000000..c19aa628d Binary files /dev/null and b/docs/screencasts/03_drug_selection.gif differ diff --git a/docs/screencasts/03_drug_selection.mp4 b/docs/screencasts/03_drug_selection.mp4 new file mode 100644 index 000000000..dc9c58c85 Binary files /dev/null and b/docs/screencasts/03_drug_selection.mp4 differ diff --git a/docs/screencasts/04_tutorial.gif b/docs/screencasts/04_tutorial.gif new file mode 100644 index 000000000..ed6bae973 Binary files /dev/null and b/docs/screencasts/04_tutorial.gif differ diff --git a/docs/screencasts/04_tutorial.mp4 b/docs/screencasts/04_tutorial.mp4 new file mode 100644 index 000000000..2bffb1894 Binary files /dev/null and b/docs/screencasts/04_tutorial.mp4 differ diff --git a/docs/screencasts/05_bottom_navigation_loopable.gif b/docs/screencasts/05_bottom_navigation_loopable.gif new file mode 100644 index 000000000..c73e1b984 Binary files /dev/null and b/docs/screencasts/05_bottom_navigation_loopable.gif differ diff --git a/docs/screencasts/05_bottom_navigation_loopable.mp4 b/docs/screencasts/05_bottom_navigation_loopable.mp4 new file mode 100644 index 000000000..8d3437726 Binary files /dev/null and b/docs/screencasts/05_bottom_navigation_loopable.mp4 differ diff --git a/docs/screencasts/06_drug_search_and_filter_loopable.gif b/docs/screencasts/06_drug_search_and_filter_loopable.gif new file mode 100644 index 000000000..14a4c58e0 Binary files /dev/null and b/docs/screencasts/06_drug_search_and_filter_loopable.gif differ diff --git a/docs/screencasts/06_drug_search_and_filter_loopable.mp4 b/docs/screencasts/06_drug_search_and_filter_loopable.mp4 new file mode 100644 index 000000000..a4a0b9897 Binary files /dev/null and b/docs/screencasts/06_drug_search_and_filter_loopable.mp4 differ diff --git a/docs/screencasts/07_ibuprofen_loopable.gif b/docs/screencasts/07_ibuprofen_loopable.gif new file mode 100644 index 000000000..7a95615c3 Binary files /dev/null and b/docs/screencasts/07_ibuprofen_loopable.gif differ diff --git a/docs/screencasts/07_ibuprofen_loopable.mp4 b/docs/screencasts/07_ibuprofen_loopable.mp4 new file mode 100644 index 000000000..1df174552 Binary files /dev/null and b/docs/screencasts/07_ibuprofen_loopable.mp4 differ diff --git a/docs/screencasts/08_report_and_cyp2c9_loopable.gif b/docs/screencasts/08_report_and_cyp2c9_loopable.gif new file mode 100644 index 000000000..e1856b761 Binary files /dev/null and b/docs/screencasts/08_report_and_cyp2c9_loopable.gif differ diff --git a/docs/screencasts/08_report_and_cyp2c9_loopable.mp4 b/docs/screencasts/08_report_and_cyp2c9_loopable.mp4 new file mode 100644 index 000000000..bb691d9d2 Binary files /dev/null and b/docs/screencasts/08_report_and_cyp2c9_loopable.mp4 differ diff --git a/docs/screencasts/09_faq_and_more_loopable.gif b/docs/screencasts/09_faq_and_more_loopable.gif new file mode 100644 index 000000000..e2f545fa4 Binary files /dev/null and b/docs/screencasts/09_faq_and_more_loopable.gif differ diff --git a/docs/screencasts/09_faq_and_more_loopable.mp4 b/docs/screencasts/09_faq_and_more_loopable.mp4 new file mode 100644 index 000000000..33fa058c6 Binary files /dev/null and b/docs/screencasts/09_faq_and_more_loopable.mp4 differ diff --git a/docs/screencasts/10_delete_data.gif b/docs/screencasts/10_delete_data.gif new file mode 100644 index 000000000..eba8d9a6e Binary files /dev/null and b/docs/screencasts/10_delete_data.gif differ diff --git a/docs/screencasts/10_delete_data.mp4 b/docs/screencasts/10_delete_data.mp4 new file mode 100644 index 000000000..4f1b3cf88 Binary files /dev/null and b/docs/screencasts/10_delete_data.mp4 differ diff --git a/docs/screencasts/full.mov b/docs/screencasts/full.mov new file mode 100644 index 000000000..185da9328 Binary files /dev/null and b/docs/screencasts/full.mov differ diff --git a/docs/screencasts/full_clean.mp4 b/docs/screencasts/full_clean.mp4 new file mode 100644 index 000000000..9c7acea2f Binary files /dev/null and b/docs/screencasts/full_clean.mp4 differ diff --git a/docs/screenshots/about-us.png b/docs/screenshots/about-us.png deleted file mode 100644 index 241005b78..000000000 Binary files a/docs/screenshots/about-us.png and /dev/null differ diff --git a/docs/screenshots/accept-terms.png b/docs/screenshots/accept-terms.png new file mode 100644 index 000000000..4cf78dee7 Binary files /dev/null and b/docs/screenshots/accept-terms.png differ diff --git a/docs/screenshots/cyp2c19.png b/docs/screenshots/cyp2c19.png deleted file mode 100644 index d6384872b..000000000 Binary files a/docs/screenshots/cyp2c19.png and /dev/null differ diff --git a/docs/screenshots/cyp2c9-expanded.png b/docs/screenshots/cyp2c9-expanded.png new file mode 100644 index 000000000..548c9c67d Binary files /dev/null and b/docs/screenshots/cyp2c9-expanded.png differ diff --git a/docs/screenshots/cyp2d6.png b/docs/screenshots/cyp2d6.png deleted file mode 100644 index ca5d7ab95..000000000 Binary files a/docs/screenshots/cyp2d6.png and /dev/null differ diff --git a/docs/screenshots/delete-app-data.png b/docs/screenshots/delete-app-data.png index c4b0f0eeb..6039e5566 100644 Binary files a/docs/screenshots/delete-app-data.png and b/docs/screenshots/delete-app-data.png differ diff --git a/docs/screenshots/drug-search-filter.png b/docs/screenshots/drug-search-filter.png index a52dc8a17..0a47444ce 100644 Binary files a/docs/screenshots/drug-search-filter.png and b/docs/screenshots/drug-search-filter.png differ diff --git a/docs/screenshots/drug-search-tooltip.png b/docs/screenshots/drug-search-tooltip.png deleted file mode 100644 index 8655d5df1..000000000 Binary files a/docs/screenshots/drug-search-tooltip.png and /dev/null differ diff --git a/docs/screenshots/drug-selection-intro.png b/docs/screenshots/drug-selection-intro.png new file mode 100644 index 000000000..13aa42878 Binary files /dev/null and b/docs/screenshots/drug-selection-intro.png differ diff --git a/docs/screenshots/drug-selection.png b/docs/screenshots/drug-selection.png index ecb633c87..6ef302d02 100644 Binary files a/docs/screenshots/drug-selection.png and b/docs/screenshots/drug-selection.png differ diff --git a/docs/screenshots/faq-first-item.png b/docs/screenshots/faq-first-item.png index c8bfdb1b5..a61772aad 100644 Binary files a/docs/screenshots/faq-first-item.png and b/docs/screenshots/faq-first-item.png differ diff --git a/docs/screenshots/gene-report-all.png b/docs/screenshots/gene-report-all.png new file mode 100644 index 000000000..5e2fde0a1 Binary files /dev/null and b/docs/screenshots/gene-report-all.png differ diff --git a/docs/screenshots/onboarding-1.png b/docs/screenshots/onboarding-1.png index b82fad945..5f5736d56 100644 Binary files a/docs/screenshots/onboarding-1.png and b/docs/screenshots/onboarding-1.png differ diff --git a/docs/screenshots/onboarding-2.png b/docs/screenshots/onboarding-2.png index a828e6af6..8b50a3367 100644 Binary files a/docs/screenshots/onboarding-2.png and b/docs/screenshots/onboarding-2.png differ diff --git a/docs/screenshots/onboarding-3.png b/docs/screenshots/onboarding-3.png index 5033006e5..0ac37146c 100644 Binary files a/docs/screenshots/onboarding-3.png and b/docs/screenshots/onboarding-3.png differ diff --git a/docs/screenshots/onboarding-4.png b/docs/screenshots/onboarding-4.png index 5b269dea8..151993a73 100644 Binary files a/docs/screenshots/onboarding-4.png and b/docs/screenshots/onboarding-4.png differ diff --git a/docs/screenshots/onboarding-5.png b/docs/screenshots/onboarding-5.png index 1167712c4..d8b4da86d 100644 Binary files a/docs/screenshots/onboarding-5.png and b/docs/screenshots/onboarding-5.png differ diff --git a/docs/screenshots/setup-complete.png b/docs/screenshots/setup-complete.png new file mode 100644 index 000000000..6ee90a8ab Binary files /dev/null and b/docs/screenshots/setup-complete.png differ diff --git a/docs/screenshots/tutorial-1.png b/docs/screenshots/tutorial-1.png new file mode 100644 index 000000000..b29b01b42 Binary files /dev/null and b/docs/screenshots/tutorial-1.png differ diff --git a/docs/screenshots/tutorial-2.png b/docs/screenshots/tutorial-2.png new file mode 100644 index 000000000..b54bb84fc Binary files /dev/null and b/docs/screenshots/tutorial-2.png differ diff --git a/docs/screenshots/tutorial-3.png b/docs/screenshots/tutorial-3.png new file mode 100644 index 000000000..b2e03f90a Binary files /dev/null and b/docs/screenshots/tutorial-3.png differ diff --git a/docs/screenshots/tutorial-4.png b/docs/screenshots/tutorial-4.png new file mode 100644 index 000000000..fe14d3774 Binary files /dev/null and b/docs/screenshots/tutorial-4.png differ diff --git a/docs/screenshots/tutorial-5.png b/docs/screenshots/tutorial-5.png new file mode 100644 index 000000000..dd897c978 Binary files /dev/null and b/docs/screenshots/tutorial-5.png differ